Skip to content

Commit e4a8fe2

Browse files
authored
Merge pull request #8 from h908714124/supplierless
allow mapping with Function and Collector directly, without wrapping …
2 parents 7e6c0db + e4763fe commit e4a8fe2

29 files changed

+676
-170
lines changed

annotations/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
/dependency-reduced-pom.xml
66
/.gradle
77
/build
8+
/out

annotations/src/main/java/net/jbock/Parameter.java

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import java.lang.annotation.Retention;
55
import java.lang.annotation.RetentionPolicy;
66
import java.lang.annotation.Target;
7-
import java.util.function.Supplier;
87

98
/**
109
* <h3>Marker for parameter methods</h3>
@@ -56,8 +55,8 @@
5655
* <h3>Optional custom mapper</h3>
5756
*
5857
* <p>
59-
* The mapper is a {@link java.util.function.Supplier Supplier} that returns a
60-
* {@link java.util.function.Function Function&lt;String, X&gt;}.
58+
* The mapper is a either a {@link java.util.function.Function Function&lt;String, X&gt;}
59+
* or a {@link java.util.function.Supplier Supplier} that returns such a function.
6160
* The return value {@code X} is called the <em>mapper type</em>.
6261
* The parameter method must return {@code X}, or {@code Optional<X>} if the
6362
* parameter is {@link #optional()}, or {@code List<X>} if the parameter is
@@ -69,58 +68,57 @@
6968
* </p>
7069
*
7170
* <pre>{@code
72-
* class PositiveNumberMapper implements Supplier&lt;Function&lt;String, Integer&gt;&gt; {
73-
*
74-
* public Function&lt;String, Integer&gt; get() {
75-
* return s -> {
76-
* Integer r = Integer.valueOf(s);
77-
* if (r <= 0) {
78-
* throw new IllegalArgumentException("Positive number expected");
79-
* }
80-
* return r;
71+
* class PositiveNumberMapper implements Function<String, Integer> {
72+
*
73+
* public Integer apply(String s) {
74+
* Integer r = Integer.valueOf(s);
75+
* if (r <= 0) {
76+
* throw new IllegalArgumentException("Positive number expected");
8177
* }
78+
* return r;
8279
* }
8380
* }
8481
* }</pre>
8582
*
86-
* @return an optional mapper class
83+
* @return a mapper class
8784
*/
88-
Class<? extends Supplier> mappedBy() default Supplier.class;
85+
Class<?> mappedBy() default Object.class;
8986

9087
/**
9188
* <h3>Optional custom collector</h3>
9289
*
9390
* <p>
94-
* The supplier must return a {@link java.util.stream.Collector Collector&lt;M, ?, X&gt;}
95-
* where {@code X} is the parameter type, and {@code M} is the <em>mapper type</em>.
91+
* This is either a {@link java.util.stream.Collector Collector&lt;M, ?, X&gt;}
92+
* where {@code X} is the parameter type and {@code M} is the <em>mapper type</em>,
93+
* or a {@link java.util.function.Supplier Supplier} that returns such a collector.
9694
* </p>
9795
*
9896
* <p>
9997
* For example, the following collector creates a {@code Set}:
10098
* </p>
10199
*
102100
* <pre>{@code
103-
* class ToSetCollector&lt;E&gt; implements Supplier&lt;Collector&lt;E, ?, Set&lt;E&gt;&gt;&gt; {
101+
* class ToSetCollector<E>; implements Supplier<Collector<E, ?, Set<E>>> {
104102
*
105-
* public Collector&lt;E, ?, Set&lt;E&gt;&gt; get() {
103+
* public Collector<E, ?, Set<E>> get() {
106104
* return Collectors.toSet();
107105
* }
108106
* }
109107
* }</pre>
110108
*
111-
* @return an optional collector class
109+
* @return an collector class
112110
*/
113-
Class<? extends Supplier> collectedBy() default Supplier.class;
111+
Class<?> collectedBy() default Object.class;
114112

115113
/**
116-
* <p>Declares this parameter repeatable.</p>
114+
* <p>Declares this parameter as repeatable.</p>
117115
*
118116
* @return true if this parameter is repeatable
119117
*/
120118
boolean repeatable() default false;
121119

122120
/**
123-
* <p>Declares this parameter optional.</p>
121+
* <p>Declares this parameter as optional.</p>
124122
*
125123
* <p>
126124
* <em>Note:</em>
@@ -135,7 +133,7 @@
135133

136134
/**
137135
* <p>Declares a parameter that doesn't take an argument.
138-
* For example, the following shell command contains a flag:</p>
136+
* For example, the following shell command contains the flag {@code -l}:</p>
139137
*
140138
* <pre>{@code
141139
* ls -l
@@ -159,3 +157,4 @@
159157
*/
160158
String bundleKey() default "";
161159
}
160+

annotations/src/main/java/net/jbock/PositionalParameter.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,28 +53,28 @@
5353
* Optional custom mapper.
5454
* See {@link Parameter#mappedBy()}.
5555
*
56-
* @return an optional mapper class
56+
* @return a mapper class
5757
*/
58-
Class<? extends Supplier> mappedBy() default Supplier.class;
58+
Class<?> mappedBy() default Object.class;
5959

6060
/**
6161
* Optional custom collector.
6262
* See {@link Parameter#collectedBy()}.
6363
*
64-
* @return an optional collector class
64+
* @return a collector class
6565
*/
66-
Class<? extends Supplier> collectedBy() default Supplier.class;
66+
Class<?> collectedBy() default Object.class;
6767

6868
/**
69-
* Declares this parameter repeatable.
69+
* Declares this parameter as repeatable.
7070
* See {@link Parameter#repeatable()}.
7171
*
7272
* @return true if this parameter is repeatable
7373
*/
7474
boolean repeatable() default false;
7575

7676
/**
77-
* Declares this parameter optional.
77+
* Declares this parameter as optional.
7878
* See {@link Parameter#optional()}.
7979
*
8080
* @return true if this parameter is optional
@@ -89,3 +89,4 @@
8989
*/
9090
String bundleKey() default "";
9191
}
92+

core/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ repositories {
1515

1616
dependencies {
1717
implementation 'com.squareup:javapoet:1.11.1'
18-
compile 'com.github.h908714124:jbock-annotations:2.2'
18+
compile 'com.github.h908714124:jbock-annotations:2.3'
1919
testImplementation 'com.google.testing.compile:compile-testing:0.18'
2020
testImplementation 'org.mockito:mockito-core:3.0.0'
2121
testImplementation 'org.junit.jupiter:junit-jupiter:5.5.1'

core/src/main/java/net/jbock/coerce/CoercionProvider.java

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package net.jbock.coerce;
22

3+
import com.squareup.javapoet.ClassName;
34
import com.squareup.javapoet.ParameterSpec;
5+
import com.squareup.javapoet.ParameterizedTypeName;
46
import com.squareup.javapoet.TypeName;
57
import net.jbock.coerce.hint.HintProvider;
68
import net.jbock.coerce.mappers.CoercionFactory;
@@ -14,7 +16,9 @@
1416
import javax.lang.model.type.TypeMirror;
1517
import java.util.List;
1618
import java.util.Optional;
19+
import java.util.function.Function;
1720

21+
import static net.jbock.compiler.Constants.STRING;
1822
import static net.jbock.compiler.Util.snakeToCamel;
1923

2024
public class CoercionProvider {
@@ -44,7 +48,7 @@ private Coercion run() {
4448
if (basicInfo.isRepeatable()) {
4549
return handleRepeatable();
4650
} else {
47-
return handle();
51+
return handleNotRepeatable();
4852
}
4953
} catch (UnknownTypeException e) {
5054
Optional<String> hint = HintProvider.instance().findHint(basicInfo);
@@ -53,11 +57,11 @@ private Coercion run() {
5357
}
5458
}
5559

56-
private Coercion handle() throws UnknownTypeException {
60+
private Coercion handleNotRepeatable() throws UnknownTypeException {
5761
if (basicInfo.mapperClass().isPresent()) {
58-
return handleExplicitMapper(basicInfo.mapperClass().get());
62+
return handleExplicitMapperNotRepeatable(basicInfo.mapperClass().get());
5963
} else {
60-
return handleAutoMapper();
64+
return handleAutoMapperNotRepeatable();
6165
}
6266
}
6367

@@ -69,17 +73,24 @@ private Coercion handleRepeatable() throws UnknownTypeException {
6973
}
7074
}
7175

72-
private Coercion handleAutoMapper() throws UnknownTypeException {
76+
private Coercion handleAutoMapperNotRepeatable() throws UnknownTypeException {
7377
CoercionFactory factory = findCoercion(basicInfo.optionalInfo().orElse(basicInfo.returnType()));
7478
return factory.getCoercion(basicInfo, Optional.empty());
7579
}
7680

77-
private Coercion handleExplicitMapper(TypeElement mapperClass) {
81+
private Coercion handleExplicitMapperNotRepeatable(TypeElement mapperClass) {
7882
TypeMirror returnType = basicInfo.returnType();
79-
ParameterSpec mapperParam = ParameterSpec.builder(TypeName.get(mapperClass.asType()), snakeToCamel(basicInfo.paramName()) + "Mapper").build();
8083
TypeMirror mapperReturnType = basicInfo.optionalInfo().orElse(returnType);
81-
MapperClassValidator.checkReturnType(mapperClass, mapperReturnType, basicInfo);
82-
return MapperCoercion.create(mapperReturnType, Optional.empty(), mapperParam, mapperClass.asType(), basicInfo);
84+
ParameterSpec mapperParam = mapperParam(mapperReturnType);
85+
MapperType mapperType = new MapperClassValidator(basicInfo, mapperReturnType).checkReturnType(mapperClass);
86+
return MapperCoercion.create(mapperReturnType, Optional.empty(), mapperParam, mapperType, basicInfo);
87+
}
88+
89+
private ParameterSpec mapperParam(TypeMirror mapperOutputType) {
90+
ParameterizedTypeName mapperParamType = ParameterizedTypeName.get(
91+
ClassName.get(Function.class), STRING,
92+
TypeName.get(mapperOutputType));
93+
return ParameterSpec.builder(mapperParamType, snakeToCamel(basicInfo.paramName()) + "Mapper").build();
8394
}
8495

8596
private Coercion handleRepeatableAutoMapper() throws UnknownTypeException {
@@ -91,9 +102,9 @@ private Coercion handleRepeatableAutoMapper() throws UnknownTypeException {
91102
private Coercion handleRepeatableExplicitMapper(
92103
TypeElement mapperClass) {
93104
CollectorInfo collectorInfo = collectorInfo();
94-
MapperClassValidator.checkReturnType(mapperClass, collectorInfo.inputType, basicInfo);
95-
ParameterSpec mapperParam = ParameterSpec.builder(TypeName.get(mapperClass.asType()), snakeToCamel(basicInfo.paramName()) + "Mapper").build();
96-
return MapperCoercion.create(collectorInfo.inputType, collectorInfo.collectorType(), mapperParam, mapperClass.asType(), basicInfo);
105+
MapperType mapperType = new MapperClassValidator(basicInfo, collectorInfo.inputType).checkReturnType(mapperClass);
106+
ParameterSpec mapperParam = mapperParam(collectorInfo.inputType);
107+
return MapperCoercion.create(collectorInfo.inputType, collectorInfo.collectorType(), mapperParam, mapperType, basicInfo);
97108
}
98109

99110
private CoercionFactory findCoercion(TypeMirror mirror) throws UnknownTypeException {
@@ -127,7 +138,7 @@ private boolean isEnumType(TypeMirror mirror) {
127138

128139
private CollectorInfo collectorInfo() {
129140
if (basicInfo.collectorClass().isPresent()) {
130-
return CollectorClassValidator.getCollectorInfo(basicInfo.collectorClass().get(), basicInfo);
141+
return new CollectorClassValidator(basicInfo).getCollectorInfo(basicInfo.collectorClass().get());
131142
}
132143
if (!tool().isSameErasure(basicInfo.returnType(), List.class)) {
133144
throw basicInfo.asValidationException("Either define a custom collector, or return List.");
Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package net.jbock.coerce;
22

3+
import net.jbock.compiler.TypeTool;
34
import net.jbock.compiler.ValidationException;
45

56
import javax.lang.model.element.TypeElement;
67
import javax.lang.model.type.TypeMirror;
8+
import java.util.List;
79
import java.util.Map;
10+
import java.util.Optional;
811
import java.util.function.Supplier;
912
import java.util.stream.Collector;
1013

@@ -13,44 +16,55 @@
1316

1417
class CollectorClassValidator {
1518

19+
private final BasicInfo basicInfo;
20+
21+
CollectorClassValidator(BasicInfo basicInfo) {
22+
this.basicInfo = basicInfo;
23+
}
24+
1625
// visible for testing
17-
static CollectorInfo getCollectorInfo(
18-
TypeElement collectorClass,
19-
BasicInfo basicInfo) {
26+
CollectorInfo getCollectorInfo(TypeElement collectorClass) {
2027
commonChecks(basicInfo, collectorClass, "collector");
21-
TypeMirror collectorType = getCollectorType(collectorClass, basicInfo);
22-
TypeMirror t = asDeclared(collectorType).getTypeArguments().get(0);
23-
TypeMirror r = asDeclared(collectorType).getTypeArguments().get(2);
24-
Map<String, TypeMirror> solution = basicInfo.tool().unify(basicInfo.returnType(), r)
25-
.orElseThrow(() -> boom(basicInfo, String.format("The collector should return %s but returns %s", basicInfo.returnType(), r)));
26-
TypeMirror collectorClassSolved = basicInfo.tool().substitute(collectorClass.asType(), solution);
27-
if (collectorClassSolved == null) {
28-
throw boom(basicInfo, "Invalid bounds");
28+
CollectorType collectorType = getCollectorType(collectorClass);
29+
TypeMirror t = asDeclared(collectorType.type()).getTypeArguments().get(0);
30+
TypeMirror r = asDeclared(collectorType.type()).getTypeArguments().get(2);
31+
Map<String, TypeMirror> solution = tool().unify(basicInfo.returnType(), r)
32+
.orElseThrow(() -> boom(String.format("The collector should return %s but returns %s", basicInfo.returnType(), r)));
33+
if (tool().substitute(collectorClass.asType(), solution) == null) {
34+
throw boom("Invalid bounds");
2935
}
30-
TypeMirror substitute = basicInfo.tool().substitute(t, solution);
31-
if (substitute == null) {
32-
throw boom(basicInfo, "Unexpected: can solve R but not Collector<T, ?, R>");
36+
TypeMirror collectorInput = tool().substitute(t, solution);
37+
if (collectorInput == null) {
38+
throw boom("Unexpected: can solve R but not Collector<T, ?, R>");
3339
}
34-
return CollectorInfo.create(substitute, collectorClassSolved);
40+
return CollectorInfo.create(collectorInput, collectorType);
3541
}
3642

37-
private static TypeMirror getCollectorType(TypeElement collectorClass, BasicInfo basicInfo) {
38-
Resolver resolver = Resolver.resolve(basicInfo.tool().asType(Supplier.class), collectorClass.asType(), basicInfo.tool());
39-
TypeMirror typeMirror = resolver.resolveTypevars().orElseThrow(() -> boom(basicInfo, "not a Supplier"));
40-
if (basicInfo.tool().isRawType(typeMirror)) {
41-
throw boom(basicInfo, "the supplier must be parameterized");
42-
}
43-
TypeMirror collectorType = asDeclared(typeMirror).getTypeArguments().get(0);
44-
if (!basicInfo.tool().isSameErasure(collectorType, Collector.class)) {
45-
throw boom(basicInfo, "the supplier must supply a Collector");
43+
private CollectorType getCollectorType(TypeElement collectorClass) {
44+
Optional<TypeMirror> supplier = Resolver.typecheck(
45+
Supplier.class,
46+
collectorClass.asType(),
47+
basicInfo.tool());
48+
if (supplier.isPresent()) {
49+
List<? extends TypeMirror> typeArgs = asDeclared(supplier.get()).getTypeArguments();
50+
if (typeArgs.isEmpty()) {
51+
throw boom("raw Supplier type");
52+
}
53+
return CollectorType.create(basicInfo, typeArgs.get(0), true, collectorClass);
4654
}
47-
if (basicInfo.tool().isRawType(collectorType)) {
48-
throw boom(basicInfo, "the collector type must be parameterized");
49-
}
50-
return collectorType;
55+
TypeMirror collector = Resolver.typecheck(
56+
Collector.class,
57+
collectorClass.asType(),
58+
basicInfo.tool()).orElseThrow(() ->
59+
boom("not a Collector or Supplier<Collector>"));
60+
return CollectorType.create(basicInfo, collector, false, collectorClass);
61+
}
62+
63+
private TypeTool tool() {
64+
return basicInfo.tool();
5165
}
5266

53-
private static ValidationException boom(BasicInfo basicInfo, String message) {
67+
private ValidationException boom(String message) {
5468
return basicInfo.asValidationException(String.format("There is a problem with the collector class: %s.", message));
5569
}
5670
}

core/src/main/java/net/jbock/coerce/CollectorInfo.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,26 @@
33
import javax.lang.model.type.TypeMirror;
44
import java.util.Optional;
55

6-
class CollectorInfo {
6+
public class CollectorInfo {
77

88
final TypeMirror inputType;
99

10-
private final Optional<TypeMirror> collectorType;
10+
private final Optional<CollectorType> collectorType;
1111

12-
private CollectorInfo(TypeMirror inputType, Optional<TypeMirror> collectorType) {
12+
private CollectorInfo(TypeMirror inputType, Optional<CollectorType> collectorType) {
1313
this.inputType = inputType;
1414
this.collectorType = collectorType;
1515
}
1616

17-
static CollectorInfo create(TypeMirror inputType, TypeMirror collectorType) {
17+
static CollectorInfo create(TypeMirror inputType, CollectorType collectorType) {
1818
return new CollectorInfo(inputType, Optional.of(collectorType));
1919
}
2020

2121
static CollectorInfo listCollector(TypeMirror inputType) {
2222
return new CollectorInfo(inputType, Optional.empty());
2323
}
2424

25-
Optional<TypeMirror> collectorType() {
25+
Optional<CollectorType> collectorType() {
2626
return collectorType;
2727
}
2828

0 commit comments

Comments
 (0)