Skip to content

Commit

Permalink
Merge pull request #8 from h908714124/supplierless
Browse files Browse the repository at this point in the history
allow mapping with Function and Collector directly, without wrapping …
  • Loading branch information
h908714124 authored Aug 28, 2019
2 parents 7e6c0db + e4763fe commit e4a8fe2
Show file tree
Hide file tree
Showing 29 changed files with 676 additions and 170 deletions.
1 change: 1 addition & 0 deletions annotations/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
/dependency-reduced-pom.xml
/.gradle
/build
/out
45 changes: 22 additions & 23 deletions annotations/src/main/java/net/jbock/Parameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.Supplier;

/**
* <h3>Marker for parameter methods</h3>
Expand Down Expand Up @@ -56,8 +55,8 @@
* <h3>Optional custom mapper</h3>
*
* <p>
* The mapper is a {@link java.util.function.Supplier Supplier} that returns a
* {@link java.util.function.Function Function&lt;String, X&gt;}.
* The mapper is a either a {@link java.util.function.Function Function&lt;String, X&gt;}
* or a {@link java.util.function.Supplier Supplier} that returns such a function.
* The return value {@code X} is called the <em>mapper type</em>.
* The parameter method must return {@code X}, or {@code Optional<X>} if the
* parameter is {@link #optional()}, or {@code List<X>} if the parameter is
Expand All @@ -69,58 +68,57 @@
* </p>
*
* <pre>{@code
* class PositiveNumberMapper implements Supplier&lt;Function&lt;String, Integer&gt;&gt; {
*
* public Function&lt;String, Integer&gt; get() {
* return s -> {
* Integer r = Integer.valueOf(s);
* if (r <= 0) {
* throw new IllegalArgumentException("Positive number expected");
* }
* return r;
* class PositiveNumberMapper implements Function<String, Integer> {
*
* public Integer apply(String s) {
* Integer r = Integer.valueOf(s);
* if (r <= 0) {
* throw new IllegalArgumentException("Positive number expected");
* }
* return r;
* }
* }
* }</pre>
*
* @return an optional mapper class
* @return a mapper class
*/
Class<? extends Supplier> mappedBy() default Supplier.class;
Class<?> mappedBy() default Object.class;

/**
* <h3>Optional custom collector</h3>
*
* <p>
* The supplier must return a {@link java.util.stream.Collector Collector&lt;M, ?, X&gt;}
* where {@code X} is the parameter type, and {@code M} is the <em>mapper type</em>.
* This is either a {@link java.util.stream.Collector Collector&lt;M, ?, X&gt;}
* where {@code X} is the parameter type and {@code M} is the <em>mapper type</em>,
* or a {@link java.util.function.Supplier Supplier} that returns such a collector.
* </p>
*
* <p>
* For example, the following collector creates a {@code Set}:
* </p>
*
* <pre>{@code
* class ToSetCollector&lt;E&gt; implements Supplier&lt;Collector&lt;E, ?, Set&lt;E&gt;&gt;&gt; {
* class ToSetCollector<E>; implements Supplier<Collector<E, ?, Set<E>>> {
*
* public Collector&lt;E, ?, Set&lt;E&gt;&gt; get() {
* public Collector<E, ?, Set<E>> get() {
* return Collectors.toSet();
* }
* }
* }</pre>
*
* @return an optional collector class
* @return an collector class
*/
Class<? extends Supplier> collectedBy() default Supplier.class;
Class<?> collectedBy() default Object.class;

/**
* <p>Declares this parameter repeatable.</p>
* <p>Declares this parameter as repeatable.</p>
*
* @return true if this parameter is repeatable
*/
boolean repeatable() default false;

/**
* <p>Declares this parameter optional.</p>
* <p>Declares this parameter as optional.</p>
*
* <p>
* <em>Note:</em>
Expand All @@ -135,7 +133,7 @@

/**
* <p>Declares a parameter that doesn't take an argument.
* For example, the following shell command contains a flag:</p>
* For example, the following shell command contains the flag {@code -l}:</p>
*
* <pre>{@code
* ls -l
Expand All @@ -159,3 +157,4 @@
*/
String bundleKey() default "";
}

13 changes: 7 additions & 6 deletions annotations/src/main/java/net/jbock/PositionalParameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,28 +53,28 @@
* Optional custom mapper.
* See {@link Parameter#mappedBy()}.
*
* @return an optional mapper class
* @return a mapper class
*/
Class<? extends Supplier> mappedBy() default Supplier.class;
Class<?> mappedBy() default Object.class;

/**
* Optional custom collector.
* See {@link Parameter#collectedBy()}.
*
* @return an optional collector class
* @return a collector class
*/
Class<? extends Supplier> collectedBy() default Supplier.class;
Class<?> collectedBy() default Object.class;

/**
* Declares this parameter repeatable.
* Declares this parameter as repeatable.
* See {@link Parameter#repeatable()}.
*
* @return true if this parameter is repeatable
*/
boolean repeatable() default false;

/**
* Declares this parameter optional.
* Declares this parameter as optional.
* See {@link Parameter#optional()}.
*
* @return true if this parameter is optional
Expand All @@ -89,3 +89,4 @@
*/
String bundleKey() default "";
}

2 changes: 1 addition & 1 deletion core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ repositories {

dependencies {
implementation 'com.squareup:javapoet:1.11.1'
compile 'com.github.h908714124:jbock-annotations:2.2'
compile 'com.github.h908714124:jbock-annotations:2.3'
testImplementation 'com.google.testing.compile:compile-testing:0.18'
testImplementation 'org.mockito:mockito-core:3.0.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.5.1'
Expand Down
37 changes: 24 additions & 13 deletions core/src/main/java/net/jbock/coerce/CoercionProvider.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package net.jbock.coerce;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import net.jbock.coerce.hint.HintProvider;
import net.jbock.coerce.mappers.CoercionFactory;
Expand All @@ -14,7 +16,9 @@
import javax.lang.model.type.TypeMirror;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

import static net.jbock.compiler.Constants.STRING;
import static net.jbock.compiler.Util.snakeToCamel;

public class CoercionProvider {
Expand Down Expand Up @@ -44,7 +48,7 @@ private Coercion run() {
if (basicInfo.isRepeatable()) {
return handleRepeatable();
} else {
return handle();
return handleNotRepeatable();
}
} catch (UnknownTypeException e) {
Optional<String> hint = HintProvider.instance().findHint(basicInfo);
Expand All @@ -53,11 +57,11 @@ private Coercion run() {
}
}

private Coercion handle() throws UnknownTypeException {
private Coercion handleNotRepeatable() throws UnknownTypeException {
if (basicInfo.mapperClass().isPresent()) {
return handleExplicitMapper(basicInfo.mapperClass().get());
return handleExplicitMapperNotRepeatable(basicInfo.mapperClass().get());
} else {
return handleAutoMapper();
return handleAutoMapperNotRepeatable();
}
}

Expand All @@ -69,17 +73,24 @@ private Coercion handleRepeatable() throws UnknownTypeException {
}
}

private Coercion handleAutoMapper() throws UnknownTypeException {
private Coercion handleAutoMapperNotRepeatable() throws UnknownTypeException {
CoercionFactory factory = findCoercion(basicInfo.optionalInfo().orElse(basicInfo.returnType()));
return factory.getCoercion(basicInfo, Optional.empty());
}

private Coercion handleExplicitMapper(TypeElement mapperClass) {
private Coercion handleExplicitMapperNotRepeatable(TypeElement mapperClass) {
TypeMirror returnType = basicInfo.returnType();
ParameterSpec mapperParam = ParameterSpec.builder(TypeName.get(mapperClass.asType()), snakeToCamel(basicInfo.paramName()) + "Mapper").build();
TypeMirror mapperReturnType = basicInfo.optionalInfo().orElse(returnType);
MapperClassValidator.checkReturnType(mapperClass, mapperReturnType, basicInfo);
return MapperCoercion.create(mapperReturnType, Optional.empty(), mapperParam, mapperClass.asType(), basicInfo);
ParameterSpec mapperParam = mapperParam(mapperReturnType);
MapperType mapperType = new MapperClassValidator(basicInfo, mapperReturnType).checkReturnType(mapperClass);
return MapperCoercion.create(mapperReturnType, Optional.empty(), mapperParam, mapperType, basicInfo);
}

private ParameterSpec mapperParam(TypeMirror mapperOutputType) {
ParameterizedTypeName mapperParamType = ParameterizedTypeName.get(
ClassName.get(Function.class), STRING,
TypeName.get(mapperOutputType));
return ParameterSpec.builder(mapperParamType, snakeToCamel(basicInfo.paramName()) + "Mapper").build();
}

private Coercion handleRepeatableAutoMapper() throws UnknownTypeException {
Expand All @@ -91,9 +102,9 @@ private Coercion handleRepeatableAutoMapper() throws UnknownTypeException {
private Coercion handleRepeatableExplicitMapper(
TypeElement mapperClass) {
CollectorInfo collectorInfo = collectorInfo();
MapperClassValidator.checkReturnType(mapperClass, collectorInfo.inputType, basicInfo);
ParameterSpec mapperParam = ParameterSpec.builder(TypeName.get(mapperClass.asType()), snakeToCamel(basicInfo.paramName()) + "Mapper").build();
return MapperCoercion.create(collectorInfo.inputType, collectorInfo.collectorType(), mapperParam, mapperClass.asType(), basicInfo);
MapperType mapperType = new MapperClassValidator(basicInfo, collectorInfo.inputType).checkReturnType(mapperClass);
ParameterSpec mapperParam = mapperParam(collectorInfo.inputType);
return MapperCoercion.create(collectorInfo.inputType, collectorInfo.collectorType(), mapperParam, mapperType, basicInfo);
}

private CoercionFactory findCoercion(TypeMirror mirror) throws UnknownTypeException {
Expand Down Expand Up @@ -127,7 +138,7 @@ private boolean isEnumType(TypeMirror mirror) {

private CollectorInfo collectorInfo() {
if (basicInfo.collectorClass().isPresent()) {
return CollectorClassValidator.getCollectorInfo(basicInfo.collectorClass().get(), basicInfo);
return new CollectorClassValidator(basicInfo).getCollectorInfo(basicInfo.collectorClass().get());
}
if (!tool().isSameErasure(basicInfo.returnType(), List.class)) {
throw basicInfo.asValidationException("Either define a custom collector, or return List.");
Expand Down
72 changes: 43 additions & 29 deletions core/src/main/java/net/jbock/coerce/CollectorClassValidator.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package net.jbock.coerce;

import net.jbock.compiler.TypeTool;
import net.jbock.compiler.ValidationException;

import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collector;

Expand All @@ -13,44 +16,55 @@

class CollectorClassValidator {

private final BasicInfo basicInfo;

CollectorClassValidator(BasicInfo basicInfo) {
this.basicInfo = basicInfo;
}

// visible for testing
static CollectorInfo getCollectorInfo(
TypeElement collectorClass,
BasicInfo basicInfo) {
CollectorInfo getCollectorInfo(TypeElement collectorClass) {
commonChecks(basicInfo, collectorClass, "collector");
TypeMirror collectorType = getCollectorType(collectorClass, basicInfo);
TypeMirror t = asDeclared(collectorType).getTypeArguments().get(0);
TypeMirror r = asDeclared(collectorType).getTypeArguments().get(2);
Map<String, TypeMirror> solution = basicInfo.tool().unify(basicInfo.returnType(), r)
.orElseThrow(() -> boom(basicInfo, String.format("The collector should return %s but returns %s", basicInfo.returnType(), r)));
TypeMirror collectorClassSolved = basicInfo.tool().substitute(collectorClass.asType(), solution);
if (collectorClassSolved == null) {
throw boom(basicInfo, "Invalid bounds");
CollectorType collectorType = getCollectorType(collectorClass);
TypeMirror t = asDeclared(collectorType.type()).getTypeArguments().get(0);
TypeMirror r = asDeclared(collectorType.type()).getTypeArguments().get(2);
Map<String, TypeMirror> solution = tool().unify(basicInfo.returnType(), r)
.orElseThrow(() -> boom(String.format("The collector should return %s but returns %s", basicInfo.returnType(), r)));
if (tool().substitute(collectorClass.asType(), solution) == null) {
throw boom("Invalid bounds");
}
TypeMirror substitute = basicInfo.tool().substitute(t, solution);
if (substitute == null) {
throw boom(basicInfo, "Unexpected: can solve R but not Collector<T, ?, R>");
TypeMirror collectorInput = tool().substitute(t, solution);
if (collectorInput == null) {
throw boom("Unexpected: can solve R but not Collector<T, ?, R>");
}
return CollectorInfo.create(substitute, collectorClassSolved);
return CollectorInfo.create(collectorInput, collectorType);
}

private static TypeMirror getCollectorType(TypeElement collectorClass, BasicInfo basicInfo) {
Resolver resolver = Resolver.resolve(basicInfo.tool().asType(Supplier.class), collectorClass.asType(), basicInfo.tool());
TypeMirror typeMirror = resolver.resolveTypevars().orElseThrow(() -> boom(basicInfo, "not a Supplier"));
if (basicInfo.tool().isRawType(typeMirror)) {
throw boom(basicInfo, "the supplier must be parameterized");
}
TypeMirror collectorType = asDeclared(typeMirror).getTypeArguments().get(0);
if (!basicInfo.tool().isSameErasure(collectorType, Collector.class)) {
throw boom(basicInfo, "the supplier must supply a Collector");
private CollectorType getCollectorType(TypeElement collectorClass) {
Optional<TypeMirror> supplier = Resolver.typecheck(
Supplier.class,
collectorClass.asType(),
basicInfo.tool());
if (supplier.isPresent()) {
List<? extends TypeMirror> typeArgs = asDeclared(supplier.get()).getTypeArguments();
if (typeArgs.isEmpty()) {
throw boom("raw Supplier type");
}
return CollectorType.create(basicInfo, typeArgs.get(0), true, collectorClass);
}
if (basicInfo.tool().isRawType(collectorType)) {
throw boom(basicInfo, "the collector type must be parameterized");
}
return collectorType;
TypeMirror collector = Resolver.typecheck(
Collector.class,
collectorClass.asType(),
basicInfo.tool()).orElseThrow(() ->
boom("not a Collector or Supplier<Collector>"));
return CollectorType.create(basicInfo, collector, false, collectorClass);
}

private TypeTool tool() {
return basicInfo.tool();
}

private static ValidationException boom(BasicInfo basicInfo, String message) {
private ValidationException boom(String message) {
return basicInfo.asValidationException(String.format("There is a problem with the collector class: %s.", message));
}
}
10 changes: 5 additions & 5 deletions core/src/main/java/net/jbock/coerce/CollectorInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,26 @@
import javax.lang.model.type.TypeMirror;
import java.util.Optional;

class CollectorInfo {
public class CollectorInfo {

final TypeMirror inputType;

private final Optional<TypeMirror> collectorType;
private final Optional<CollectorType> collectorType;

private CollectorInfo(TypeMirror inputType, Optional<TypeMirror> collectorType) {
private CollectorInfo(TypeMirror inputType, Optional<CollectorType> collectorType) {
this.inputType = inputType;
this.collectorType = collectorType;
}

static CollectorInfo create(TypeMirror inputType, TypeMirror collectorType) {
static CollectorInfo create(TypeMirror inputType, CollectorType collectorType) {
return new CollectorInfo(inputType, Optional.of(collectorType));
}

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

Optional<TypeMirror> collectorType() {
Optional<CollectorType> collectorType() {
return collectorType;
}

Expand Down
Loading

0 comments on commit e4a8fe2

Please sign in to comment.