Skip to content

Commit 6ef832a

Browse files
author
Martin
committed
The @EnumMappers annotation will now ignore duplicate enums.
This fixes #3
1 parent c302e89 commit 6ef832a

File tree

10 files changed

+311
-45
lines changed

10 files changed

+311
-45
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,6 @@ fabric.properties
5151
.gradle
5252
/local.properties
5353
.DS_Store
54-
**/build
54+
build/
5555
/captures
5656
.externalNativeBuild

enum-mapper-processor/src/main/java/com/tmtron/enums/processor/MapAllEnumsHandler.java

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@
1818
import com.google.auto.common.MoreElements;
1919
import com.google.auto.common.MoreTypes;
2020
import com.google.common.base.Optional;
21+
import com.google.common.collect.ArrayListMultimap;
2122
import com.tmtron.enums.EnumMappers;
2223

24+
import java.util.Collection;
2325
import java.util.List;
26+
import java.util.Map;
27+
import java.util.Set;
2428

2529
import javax.annotation.processing.ProcessingEnvironment;
2630
import javax.lang.model.element.AnnotationMirror;
@@ -37,47 +41,102 @@ class MapAllEnumsHandler {
3741

3842
// the EnumMappers class must have a member named value (array of Enum classes)
3943
private static final String ENUMS_ID = "value";
40-
private final AnnotationMirror annotationMirrorMapAllEnums;
4144
private final ProcessingEnvironment processingEnvironment;
42-
private final Element annotatedElement;
45+
private final Set<Element> annotatedElements;
46+
;
4347

4448
/**
4549
* Will process an element (e.g. a class) which must have the {@link EnumMappers} annotation.
4650
* Note: the {@link EnumMappers} annotation may contain multiple Enum classes in the enums class array.
4751
*
4852
* @param processingEnvironment the processing environment
49-
* @param annotatedElement the element (e.g. class) which has the {@link EnumMappers} annotation
53+
* @param annotatedElements the element/s (e.g. class) which has/have the {@link EnumMappers} annotation
5054
*/
51-
MapAllEnumsHandler(ProcessingEnvironment processingEnvironment, Element annotatedElement) {
55+
MapAllEnumsHandler(ProcessingEnvironment processingEnvironment, Set<Element> annotatedElements) {
5256
this.processingEnvironment = processingEnvironment;
53-
this.annotatedElement = annotatedElement;
57+
this.annotatedElements = annotatedElements;
58+
}
59+
60+
/* Returns a map where the keys are all the unique enumerations and the values are the annotated classes
61+
* since V1.0.2 we can use the same enum in multiple @EnumMapper annotations
62+
* see issue #3 "duplicate enums in @EnumMappers should be ignored"
63+
* https://github.com/tmtron/enum-mapper/issues/3
64+
*/
65+
private Map<AnnotationValueWrapper, Collection<Element>> getEnumAnnotationsMap() {
66+
ArrayListMultimap<AnnotationValueWrapper, Element> enumAnnotations = ArrayListMultimap.create();
67+
for (Element annotatedElement : annotatedElements) {
68+
Optional<AnnotationMirror> optMirror = MoreElements.getAnnotationMirror(annotatedElement, EnumMappers
69+
.class);
70+
if (!optMirror.isPresent()) {
71+
throw new RuntimeException(EnumMappers.class.getSimpleName() + " annotation not found!");
72+
}
73+
final AnnotationMirror annotationMirrorMapAllEnums = optMirror.get();
5474

55-
Optional<AnnotationMirror> optMirror = MoreElements.getAnnotationMirror(annotatedElement, EnumMappers.class);
56-
if (!optMirror.isPresent()) {
57-
throw new RuntimeException(EnumMappers.class.getSimpleName() + " annotation not found!");
75+
// get the "value" annotationValue (which is of type: array of classes)
76+
AnnotationValue annotationValue = AnnotationProcessingUtil.getRequiredAnnotationValue
77+
(annotationMirrorMapAllEnums, ENUMS_ID);
78+
79+
// the annotationValue is an array of classes
80+
// we convert it to a list where each item is the class
81+
// e.g. "value" -> {com.test.Dummy.ColorEnum.class, com.test.Dummy.BoolEnum.class}
82+
// --> the enumsList will contain 2 items (ColorEnum and BoolEnum)
83+
List<? extends AnnotationValue> enumsList = AnnotationProcessingUtil.asList(annotationValue.getValue(),
84+
AnnotationValue.class, ENUMS_ID);
85+
86+
// loop over each (Enum-)class in the "value" array
87+
/*
88+
* NOTE: AnnotationValue
89+
*/
90+
for (AnnotationValue enumsClassAnnotationValue : enumsList) {
91+
//noinspection ResultOfMethodCallIgnored
92+
enumAnnotations.put(new AnnotationValueWrapper(enumsClassAnnotationValue), annotatedElement);
93+
}
5894
}
59-
this.annotationMirrorMapAllEnums = optMirror.get();
95+
return enumAnnotations.asMap();
6096
}
6197

6298
void work() {
63-
// get the "value" annotationValue (which is of type: array of classes)
64-
AnnotationValue annotationValue = AnnotationProcessingUtil.getRequiredAnnotationValue
65-
(annotationMirrorMapAllEnums, ENUMS_ID);
66-
67-
// the annotationValue is an array of classes
68-
// we convert it to a list where each item is the class
69-
// e.g. "value" -> {com.test.Dummy.ColorEnum.class, com.test.Dummy.BoolEnum.class}
70-
// --> the enumsList will contain 2 items (ColorEnum and BoolEnum)
71-
List<? extends AnnotationValue> enumsList = AnnotationProcessingUtil.asList(annotationValue.getValue(),
72-
AnnotationValue.class, ENUMS_ID);
73-
74-
// loop over each (Enum-)class in the "value" array
75-
for (AnnotationValue enumsClassAnnotationValue : enumsList) {
99+
Map<AnnotationValueWrapper, Collection<Element>> enumAnnotationMap = getEnumAnnotationsMap();
100+
101+
for (AnnotationValueWrapper enumsClassAnnotationValueWrapper : enumAnnotationMap.keySet()) {
102+
AnnotationValue enumsClassAnnotationValue = enumsClassAnnotationValueWrapper.annotationValue;
76103
TypeMirror enumsClassTypeMirror = (TypeMirror) enumsClassAnnotationValue.getValue();
77104
TypeElement enumsClassTypeElement = MoreTypes.asTypeElement(enumsClassTypeMirror);
105+
106+
Collection<Element> originElements = enumAnnotationMap.get(enumsClassAnnotationValueWrapper);
107+
78108
// now process each (Enum-)class
79109
// e.g. enumsClassTypeElement.getQualifiedName() "com.test.Dummy.BoolEnum.class"
80-
new MapEnumElement(processingEnvironment, annotatedElement, enumsClassTypeElement).work();
110+
new MapEnumElement(processingEnvironment, originElements, enumsClassTypeElement).work();
111+
}
112+
}
113+
114+
/**
115+
* Simple wrapper around an {@link AnnotationValue} that uses the toString() value for equals and hashCode.
116+
*/
117+
private static class AnnotationValueWrapper {
118+
119+
final AnnotationValue annotationValue;
120+
final String stringRep;
121+
122+
private AnnotationValueWrapper(AnnotationValue annotationValue) {
123+
this.annotationValue = annotationValue;
124+
stringRep = annotationValue.toString();
125+
}
126+
127+
@Override
128+
public boolean equals(Object o) {
129+
if (this == o) return true;
130+
if (o == null || getClass() != o.getClass()) return false;
131+
132+
AnnotationValueWrapper that = (AnnotationValueWrapper) o;
133+
134+
return stringRep.equals(that.stringRep);
135+
}
136+
137+
@Override
138+
public int hashCode() {
139+
return stringRep.hashCode();
81140
}
82141
}
83142

enum-mapper-processor/src/main/java/com/tmtron/enums/processor/MapAllEnumsProcessingStep.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ private void processEnumMapperAnnotation(Set<Element> annotatedElements) {
9292
TypeElement enumsClassTypeElement = MoreTypes.asTypeElement(enumsClassTypeMirror);
9393
// now process each (Enum-)class
9494
// e.g. enumsClassTypeElement.getQualifiedName() "com.test.Dummy.BoolEnum.class"
95-
new MapEnumElement(processingEnvironment, element, enumsClassTypeElement).work();
95+
new MapEnumElement(processingEnvironment, Collections.singletonList(element), enumsClassTypeElement)
96+
.work();
9697

9798
} catch (Exception e) {
9899
processingEnvironment.getMessager().printMessage(Diagnostic.Kind.ERROR
@@ -103,15 +104,11 @@ private void processEnumMapperAnnotation(Set<Element> annotatedElements) {
103104
}
104105

105106
private void processEnumMappersAnnotation(Set<Element> annotatedElements) {
106-
// the EnumMappers annotation may be present on multiple elements (classes or packages)
107-
for (Element element : annotatedElements) {
108-
try {
109-
new MapAllEnumsHandler(processingEnvironment, element).work();
110-
} catch (Exception e) {
111-
processingEnvironment.getMessager().printMessage(Diagnostic.Kind.ERROR
112-
, "Annotation processing error: " + e.getClass().getSimpleName() + "-" + e.getMessage()
113-
, element);
114-
}
107+
try {
108+
new MapAllEnumsHandler(processingEnvironment, annotatedElements).work();
109+
} catch (Exception e) {
110+
processingEnvironment.getMessager().printMessage(Diagnostic.Kind.ERROR
111+
, "Annotation processing error: " + e.getClass().getSimpleName() + "-" + e.getMessage());
115112
}
116113
}
117114
}

enum-mapper-processor/src/main/java/com/tmtron/enums/processor/MapEnumElement.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import java.io.IOException;
2323
import java.util.ArrayList;
24+
import java.util.Collection;
2425
import java.util.List;
2526

2627
import javax.annotation.processing.ProcessingEnvironment;
@@ -34,7 +35,7 @@
3435
class MapEnumElement {
3536

3637
private final ProcessingEnvironment processingEnvironment;
37-
private final Element annotatedElement;
38+
private final Collection<Element> annotatedElements;
3839
private final TypeElement enumsClassTypeElement;
3940
private final List<CodeGenEnumConst> enumConstants = new ArrayList<>();
4041
private final TypeVariableName typeVariableName4Value;
@@ -43,13 +44,13 @@ class MapEnumElement {
4344
* Will process a single Enum class of the {@link EnumMappers} annotation.
4445
*
4546
* @param processingEnvironment the processing environment
46-
* @param annotatedElement the element (e.g. class) which has the {@link EnumMappers} annotation
47+
* @param annotatedElements the element/s (e.g. classe) which has/have the {@link EnumMappers} annotation
4748
* @param enumsClassTypeElement a single Enum class from the "values" array of the {@link EnumMappers} annotation
4849
*/
49-
MapEnumElement(ProcessingEnvironment processingEnvironment, Element annotatedElement,
50+
MapEnumElement(ProcessingEnvironment processingEnvironment, Collection<Element> annotatedElements,
5051
TypeElement enumsClassTypeElement) {
5152
this.processingEnvironment = processingEnvironment;
52-
this.annotatedElement = annotatedElement;
53+
this.annotatedElements = annotatedElements;
5354
this.enumsClassTypeElement = enumsClassTypeElement;
5455
typeVariableName4Value = TypeVariableName.get("V");
5556
}
@@ -73,7 +74,7 @@ void work() {
7374
}
7475
if (enumConstants.size() < 2) throw new RuntimeException("The Enum must have at least 2 constants!");
7576

76-
WriteMapperFull writeMapperFull = new WriteMapperFull(annotatedElement, enumsClassTypeElement
77+
WriteMapperFull writeMapperFull = new WriteMapperFull(annotatedElements, enumsClassTypeElement
7778
, enumConstants, typeVariableName4Value);
7879
try {
7980
JavaFile.builder(getPackageName(), writeMapperFull.work())

enum-mapper-processor/src/main/java/com/tmtron/enums/processor/WriteMapperFull.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.tmtron.enums.EnumMapperFull;
2727

2828
import java.time.format.DateTimeFormatter;
29+
import java.util.Collection;
2930
import java.util.List;
3031

3132
import javax.annotation.Generated;
@@ -34,14 +35,14 @@
3435
import javax.lang.model.element.TypeElement;
3536

3637
class WriteMapperFull {
37-
private final Element annotatedElement;
38+
private final Collection<Element> annotatedElements;
3839
private final TypeElement enumsClassTypeElement;
3940
private final List<CodeGenEnumConst> enumConstants;
4041
private final TypeVariableName typeVariableName4Value;
4142

42-
WriteMapperFull(Element annotatedElement, TypeElement enumsClassTypeElement, List<CodeGenEnumConst>
43-
enumConstants, TypeVariableName typeVariableName4Value) {
44-
this.annotatedElement = annotatedElement;
43+
WriteMapperFull(Collection<Element> annotatedElements, TypeElement enumsClassTypeElement
44+
, List<CodeGenEnumConst> enumConstants, TypeVariableName typeVariableName4Value) {
45+
this.annotatedElements = annotatedElements;
4546
this.enumsClassTypeElement = enumsClassTypeElement;
4647
this.enumConstants = enumConstants;
4748
this.typeVariableName4Value = typeVariableName4Value;
@@ -53,7 +54,7 @@ class WriteMapperFull {
5354
* {@literal @}Generated(
5455
* value = "com.tmtron.enums.processor.EnumsAnnotationProcessor",
5556
* date = "1976-12-14T15:16:17.234+02:00",
56-
* comments = "origin=com.test.TwoEnums_Source"
57+
* comments = "origin=[com.test.TwoEnums_SourceA,com.test.TwoEnums_SourceB]"
5758
* )
5859
* </code></pre>
5960
*
@@ -65,7 +66,23 @@ private AnnotationSpec createGeneratedAnnotation(Class<?> annotationProcessorCla
6566
.addMember("value", "$S", annotationProcessorClass.getCanonicalName())
6667
.addMember("date", "$S", dateString);
6768

68-
String commentsString = "origin=" + MoreElements.asType(annotatedElement).getQualifiedName().toString();
69+
StringBuilder sbOrigin = new StringBuilder();
70+
annotatedElements.forEach(annotatedElement -> {
71+
if (sbOrigin.length() > 0) {
72+
sbOrigin.append(",");
73+
}
74+
sbOrigin.append(MoreElements.asType(annotatedElement).getQualifiedName().toString());
75+
});
76+
77+
String commentsString = "origin=";
78+
if (annotatedElements.size() > 1) {
79+
commentsString += "[";
80+
}
81+
commentsString += sbOrigin.toString();
82+
if (annotatedElements.size() > 1) {
83+
commentsString += "]";
84+
}
85+
6986
annotationSpecBuilder.addMember("comments", "$S", commentsString);
7087

7188
return annotationSpecBuilder.build();
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright © 2017 Martin Trummer (martin.trummer@tmtron.com)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.tmtron.enums.processor;
17+
18+
import org.junit.Test;
19+
20+
public class TestMappers4DuplicateEnums extends AnnotationProcessorTest {
21+
22+
/**
23+
* Test if we can put the @EnumMappers annotation handles duplicate enums gracefully.
24+
* <p>
25+
* Did not work in V1.0.1. See <a href="https://github.com/tmtron/enum-mapper/issues/3">
26+
* issue #3 "duplicate enums in @EnumMappers should be ignored"</a>
27+
* </p>
28+
*/
29+
@Test
30+
public void test() throws Exception {
31+
assertAboutEnumsProcessing(getJfoResource("DuplicateEnums_Source.java"))
32+
.compilesWithoutWarnings()
33+
.and()
34+
.generatesSources(
35+
getJfoResource("BoolEnum_MapperFull.java"),
36+
getJfoResource("ColorEnum_MapperFull.java"));
37+
}
38+
39+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright © 2017 Martin Trummer (martin.trummer@tmtron.com)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
// GENERATED
17+
package com.test;
18+
19+
import com.tmtron.enums.EnumMapperFull;
20+
21+
import javax.annotation.Generated;
22+
23+
@Generated(
24+
value = "com.tmtron.enums.processor.EnumsAnnotationProcessor",
25+
date = "1976-12-14T15:16:17.234+02:00",
26+
comments = "origin=[com.test.DuplicateEnums_Source.AnnotatedClassA,com.test.DuplicateEnums_Source" +
27+
".AnnotatedClassC]"
28+
)
29+
public final class BoolEnum_MapperFull {
30+
public static <V> IsetON<V> setOFF(V value) {
31+
StagedBuilder<V> result = new StagedBuilder<>();
32+
result.enumMapperBuilder.put(DuplicateEnums_Source.BoolEnum.OFF, value);
33+
return result;
34+
}
35+
36+
public interface IsetON<V> {
37+
EnumMapperFull<DuplicateEnums_Source.BoolEnum, V> setON(V value);
38+
}
39+
40+
private static class StagedBuilder<V> implements IsetON<V> {
41+
private final EnumMapperFull.Builder<DuplicateEnums_Source.BoolEnum, V> enumMapperBuilder = EnumMapperFull
42+
.builder
43+
(DuplicateEnums_Source.BoolEnum.class);
44+
45+
@Override
46+
public EnumMapperFull<DuplicateEnums_Source.BoolEnum, V> setON(V value) {
47+
enumMapperBuilder.put(DuplicateEnums_Source.BoolEnum.ON, value);
48+
return enumMapperBuilder.build();
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)