diff --git a/README.md b/README.md
index 00e3df8..8aae91f 100644
--- a/README.md
+++ b/README.md
@@ -8,25 +8,24 @@
Implementing and especially maintaining of fluent and immutable apis is one of a most annoying and difficult tasks to do in java developing.
You usually have to implement a lot of boilerplate code that is only needed to handle and clone the fluent apis internal state.
-Changing of an existing fluent api can therefore be a very complex thing to do.
+Refactoring of an existing fluent api can therefore be a very complex thing to do.
-This project provides an annotation processor that will generate the fluent api implementation for you and therefore completely hiding all necessary boilerplate code from you.
-All you have to do is to define a bunch of interfaces and to configure its "plumbing" by placing a few annotations.
+This project provides an annotation processor that generates fluent api implementations and therefore completely hiding all necessary boilerplate code.
+To achieve this all that needs to be done is to define some fluent and backing bean interfaces and command classes and to configure its "plumbing" by placing a few annotations.
+.
# Features
- fluent api is created by defining some interfaces and placing some annotations on them
-- our annotation processor then generates the fluent api implementation for you
+- this projects annotation processor then generates the fluent api implementation for you
+- it's possible to use converters and validators on fluent api parameters
- implementing, extending and maintaining of an immutable, fluent api becomes a no-brainer
-
# Restrictions
- fluent interfaces must not contain any cycles and must be strongly hierarchically
# How does it work?
## Project Setup
-A release will be done in the next couple of days.
-A description about how to bind dependencies will be added afterwards
The api lib must be bound as a dependency - for example in maven:
```xml
@@ -35,9 +34,18 @@ The api lib must be bound as a dependency - for example in maven:
io.toolisticon.fluapigen
fluapigen-api
- 0.4.1
+ 0.8.0
+ provided
+
+
+ io.toolisticon.fluapigen
+ fluapigen-validation-api
+ 0.8.0
+ compile
+
+
```
@@ -53,7 +61,7 @@ Additionally, you need to declare the annotation processor path in your compiler
io.toolisticon.fluapigen
fluapigen-processor
- 0.4.1
+ 0.8.0
@@ -63,18 +71,28 @@ Additionally, you need to declare the annotation processor path in your compiler
```
## Documentation
-Basically you have to create a class annotated with the _FluentApi_ annotation which has the fluent apis base class name as attribute.
-
-Then you are able to define your api inside this class via interfaces.
+Basically you have to create a class annotated with the _FluentApi_ annotation which takes the fluent apis base class name as attribute.
```java
@FluentApi("CuteFluentApiStarter")
public class CuteFluentApi {
- // Your interfaces for backing beans and fluent api and classes for closing commands.
+ // Contains the interfaces for
+ // backing beans
+ // and fluent api
+ // and classes for closing commands.
}
```
-This can be broke down to 3 different steps.
+The fluent api can be defined inside that class by using three kinds of components:
+- the fluent api interfaces
+- the backing bean interfaces which are storing the configuration data passed in via the fluent api interfaces
+- command classes which are taking the root backing bean as a parameter and doing things with it
+
+The following diagram demonstrates how those components relate to each other:
+
+![Fluapigen Setup](/documentation/Fluapigen.png)
+
+The development of the fluent api can be broke down to 3 different steps.
### Defining The Backing Bean
The backing bean interfaces are defining the configuration (or in other word context) to be build.
@@ -91,6 +109,7 @@ Field ids must be unique in the interface.
@FluentApiBackingBean
public interface CompilerTest {
+ // annotation is optional - ids default to method name
@FluentApiBackingBeanField("value1")
String getValue1();
@@ -124,7 +143,7 @@ Commands may have a return value.
### Defining The Fluent Api
The fluent api must be defined in multiple interfaces annotated with the _FluentApiInterface_ annotation.
-Those interface will be bound to one specific backing bean. A backing bean can be bound by multiple fluent interfaces.
+Each interface will be bound to one specific backing bean. A backing bean can be bound by multiple fluent interfaces.
Methods defined in those interfaces are only allowed to return other fluent api interfaces, except for closing commands.
@@ -138,6 +157,7 @@ Those interfaces methods will be available as static starter methods for the flu
Please check the following example for further information - because the code explains itself pretty well.
+
## Example
This is a small example related to the [toolisticon CUTE]() project.
@@ -402,7 +422,7 @@ public @interface Matches {
````
Validators are annotations annotated with _FluentApiValidator_ meta annotation. The _FluentApiValidator_ annotation is used to reference the validators implementation class that must implement the _Validator_ interface.
-Validation criteria can be added as annotation attribute. The _FluentApiValidator_ meta annotation defines the attribute to validator constructor mapping via the parameterNames attribute.
+Validation criteria can be added as annotation attributes. The _FluentApiValidator_ meta annotation defines the attribute to validator constructor mapping via the parameterNames attribute.
The validator implementation must provide a matching constructor.
*Remark : The feature is currently under development and not 100% done. There will still be improvements regarding processing time validation and error output*
diff --git a/documentation/Fluapigen.drawio b/documentation/Fluapigen.drawio
new file mode 100644
index 0000000..4c5afdc
--- /dev/null
+++ b/documentation/Fluapigen.drawio
@@ -0,0 +1,315 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/documentation/Fluapigen.png b/documentation/Fluapigen.png
new file mode 100644
index 0000000..280375e
Binary files /dev/null and b/documentation/Fluapigen.png differ
diff --git a/fluapigen-api/pom.xml b/fluapigen-api/pom.xml
index fae8949..8d72b24 100644
--- a/fluapigen-api/pom.xml
+++ b/fluapigen-api/pom.xml
@@ -9,7 +9,7 @@
io.toolisticon.fluapigen
fluapigen
- 0.8.0
+ 0.8.1
fluapigen-api
diff --git a/fluapigen-example/pom.xml b/fluapigen-example/pom.xml
index 91b21d3..ae8e0dc 100644
--- a/fluapigen-example/pom.xml
+++ b/fluapigen-example/pom.xml
@@ -9,7 +9,7 @@
io.toolisticon.fluapigen
fluapigen
- 0.8.0
+ 0.8.1
fluapigen-example
diff --git a/fluapigen-integrationTest/pom.xml b/fluapigen-integrationTest/pom.xml
index 2665605..91bf063 100644
--- a/fluapigen-integrationTest/pom.xml
+++ b/fluapigen-integrationTest/pom.xml
@@ -9,7 +9,7 @@
io.toolisticon.fluapigen
fluapigen
- 0.8.0
+ 0.8.1
fluapigen-integrationTest
diff --git a/fluapigen-integrationTest/src/main/java/io/toolisticon/fluapigen/integrationtest/EmailFluentApi.java b/fluapigen-integrationTest/src/main/java/io/toolisticon/fluapigen/integrationtest/EmailFluentApi.java
new file mode 100644
index 0000000..1124745
--- /dev/null
+++ b/fluapigen-integrationTest/src/main/java/io/toolisticon/fluapigen/integrationtest/EmailFluentApi.java
@@ -0,0 +1,150 @@
+package io.toolisticon.fluapigen.integrationtest;
+
+import io.toolisticon.fluapigen.api.FluentApi;
+import io.toolisticon.fluapigen.api.FluentApiBackingBean;
+import io.toolisticon.fluapigen.api.FluentApiBackingBeanField;
+import io.toolisticon.fluapigen.api.FluentApiBackingBeanMapping;
+import io.toolisticon.fluapigen.api.FluentApiCommand;
+import io.toolisticon.fluapigen.api.FluentApiImplicitValue;
+import io.toolisticon.fluapigen.api.FluentApiInlineBackingBeanMapping;
+import io.toolisticon.fluapigen.api.FluentApiInterface;
+import io.toolisticon.fluapigen.api.FluentApiParentBackingBeanMapping;
+import io.toolisticon.fluapigen.api.FluentApiRoot;
+import io.toolisticon.fluapigen.api.MappingAction;
+import io.toolisticon.fluapigen.api.TargetBackingBean;
+import io.toolisticon.fluapigen.validation.api.Matches;
+import io.toolisticon.fluapigen.validation.api.NotNull;
+
+import java.io.File;
+import java.util.List;
+
+@FluentApi("EmailFluentApiStarter")
+public class EmailFluentApi {
+
+ // ---------------------------------------------------
+ // -- Backing Beans
+ // ---------------------------------------------------
+
+ /**
+ * The root backing bean.
+ */
+ @FluentApiBackingBean
+ public interface EmailBB {
+
+ List recipients();
+
+ String subject();
+
+ String body();
+
+ List attachments();
+ }
+
+ /**
+ * Backing bean for storing recipients
+ */
+ @FluentApiBackingBean
+ public interface RecipientBB{
+ RecipientKind recipientKind();
+
+ String emailAddress();
+ }
+
+ public enum RecipientKind {
+ TO,
+ CC,
+ BCC;
+ }
+
+ /**
+ * Backing Bean for attachments
+ */
+ @FluentApiBackingBean
+ public interface AttachmentBB {
+ @FluentApiBackingBeanField()
+ File attachment();
+
+ String attachmentName();
+ }
+
+ // ---------------------------------------------------
+ // -- Commands
+ // ---------------------------------------------------
+
+ @FluentApiCommand
+ public static class SendEmailCommand {
+ public static EmailBB sendEmail(EmailBB emailBB) {
+ // Implementation for sending the email
+ return emailBB;
+ }
+ }
+
+ // ---------------------------------------------------
+ // -- Fluent Api
+ // ---------------------------------------------------
+
+
+ @FluentApiRoot
+ @FluentApiInterface(EmailBB.class)
+ public interface EmailStartInterface {
+
+ @FluentApiInlineBackingBeanMapping("recipients")
+ @FluentApiImplicitValue(value="TO", id="recipientKind", target = TargetBackingBean.INLINE)
+ AddRecipientsOrSetSubject to (
+ @FluentApiBackingBeanMapping(value = "emailAddress", target = TargetBackingBean.INLINE)
+ @NotNull @Matches(".*[@].*") String emailAddress);
+
+ @FluentApiInlineBackingBeanMapping("recipients")
+ @FluentApiImplicitValue(value="CC", id="recipientKind", target = TargetBackingBean.INLINE)
+ AddRecipientsOrSetSubject cc (@FluentApiBackingBeanMapping(value = "emailAddress", target = TargetBackingBean.INLINE)
+ @NotNull @Matches(".*[@].*") String emailAddress);
+ @FluentApiInlineBackingBeanMapping("recipients")
+ @FluentApiImplicitValue(value="BCC", id="recipientKind", target = TargetBackingBean.INLINE)
+ AddRecipientsOrSetSubject bcc (@FluentApiBackingBeanMapping(value = "emailAddress", target = TargetBackingBean.INLINE)
+ @NotNull @Matches(".*[@].*") String emailAddress);
+ }
+
+ @FluentApiInterface(EmailBB.class)
+ public interface AddRecipientsOrSetSubject {
+ EmailStartInterface and();
+
+ AddBodyInterface withSubject (@FluentApiBackingBeanMapping(value="subject") @NotNull String subject);
+
+ }
+
+ @FluentApiInterface(EmailBB.class)
+ public interface AddBodyInterface {
+
+ AddAttachmentOrCloseCommandInterface withBody(@FluentApiBackingBeanMapping(value="subject") @NotNull String body) ;
+
+ }
+
+ @FluentApiInterface(EmailBB.class)
+ public interface AddAttachmentOrCloseCommandInterface {
+
+ AddAttachmentInterface addAttachment();
+
+
+ @FluentApiCommand(SendEmailCommand.class)
+ EmailBB sendEmail();
+
+ }
+
+
+ @FluentApiInterface(AttachmentBB.class)
+ public interface AddAttachmentFileInterface {
+
+ @FluentApiParentBackingBeanMapping(value = "attachments", action = MappingAction.ADD)
+ AddAttachmentOrCloseCommandInterface fromFile(@FluentApiBackingBeanMapping(value="attachment") File file);
+ }
+
+ @FluentApiInterface(AttachmentBB.class)
+ public interface AddAttachmentInterface extends AddAttachmentFileInterface{
+
+ AddAttachmentFileInterface withCustomName (@FluentApiBackingBeanMapping(value="attachmentName") String attachmentName);
+
+ }
+
+
+
+}
diff --git a/fluapigen-integrationTest/src/test/java/io/toolisticon/fluapigen/integrationtest/EmailFluentApiTest.java b/fluapigen-integrationTest/src/test/java/io/toolisticon/fluapigen/integrationtest/EmailFluentApiTest.java
new file mode 100644
index 0000000..597009c
--- /dev/null
+++ b/fluapigen-integrationTest/src/test/java/io/toolisticon/fluapigen/integrationtest/EmailFluentApiTest.java
@@ -0,0 +1,24 @@
+package io.toolisticon.fluapigen.integrationtest;
+
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import java.io.File;
+
+public class EmailFluentApiTest {
+
+ @Test
+ public void test() {
+ EmailFluentApi.EmailBB emailBB = EmailFluentApiStarter.cc("tobias1@holisticon.de")
+ .and().to("tobias1@holisticon.de")
+ .and().cc("tobias2@holisticon.de")
+ .withSubject("Test")
+ .withBody("LOREM IPSUM")
+ .addAttachment().withCustomName("itWorks.png").fromFile(new File("abc.png"))
+ .sendEmail();
+
+ MatcherAssert.assertThat(emailBB, Matchers.notNullValue());
+ }
+
+}
diff --git a/fluapigen-processor/pom.xml b/fluapigen-processor/pom.xml
index e02a1f8..eb9d8c0 100644
--- a/fluapigen-processor/pom.xml
+++ b/fluapigen-processor/pom.xml
@@ -9,7 +9,7 @@
io.toolisticon.fluapigen
fluapigen
- 0.8.0
+ 0.8.1
fluapigen-processor
diff --git a/fluapigen-processor/src/main/java/io/toolisticon/fluapigen/processor/ModelInterfaceMethodParameter.java b/fluapigen-processor/src/main/java/io/toolisticon/fluapigen/processor/ModelInterfaceMethodParameter.java
index b0af018..1ed4f89 100644
--- a/fluapigen-processor/src/main/java/io/toolisticon/fluapigen/processor/ModelInterfaceMethodParameter.java
+++ b/fluapigen-processor/src/main/java/io/toolisticon/fluapigen/processor/ModelInterfaceMethodParameter.java
@@ -78,7 +78,7 @@ public boolean hasValidators() {
}
public List getValidators() {
- return this.parameterElement.getAnnotations().stream().filter(e -> e.asElement().hasAnnotation(FluentApiValidator.class)).map(ModelValidator::new).collect(Collectors.toList());
+ return this.parameterElement.getAnnotations().stream().filter(e -> e.asElement().hasAnnotation(FluentApiValidator.class)).map(e -> new ModelValidator(this.parameterElement, e)).collect(Collectors.toList());
}
public String getParameterName() {
@@ -207,7 +207,14 @@ private void validateConverter(ModelBackingBeanField backBeanField) {
@DeclareCompilerMessage(code = "003", enumValueName = "BB_MAPPING_COULDNT_BE_RESOLVED", message = "Field ${0} doesn't exist in mapped backing bean ${1}. It must be one of: [${2}]", processorClass = FluentApiProcessor.class)
public boolean validate() {
- boolean result = true;
+ // check validators
+ for (ModelValidator validator : this.getValidators()) {
+ if (!validator.validate()) {
+ return false;
+ }
+ }
+
+
// fluentApiBackingBeanMapping must not be null -> missing annotation
if (fluentApiBackingBeanMapping == null) {
parameterElement.compilerMessage().asError().write(
@@ -238,7 +245,7 @@ public boolean validate() {
return false;
}
- return result;
+ return true;
}
}
diff --git a/fluapigen-processor/src/main/java/io/toolisticon/fluapigen/processor/ModelValidator.java b/fluapigen-processor/src/main/java/io/toolisticon/fluapigen/processor/ModelValidator.java
index f518faf..65ebbc7 100644
--- a/fluapigen-processor/src/main/java/io/toolisticon/fluapigen/processor/ModelValidator.java
+++ b/fluapigen-processor/src/main/java/io/toolisticon/fluapigen/processor/ModelValidator.java
@@ -1,21 +1,26 @@
package io.toolisticon.fluapigen.processor;
-import io.toolisticon.aptk.common.ToolingProvider;
+import io.toolisticon.aptk.compilermessage.api.DeclareCompilerMessage;
import io.toolisticon.aptk.tools.TypeMirrorWrapper;
import io.toolisticon.aptk.tools.corematcher.AptkCoreMatchers;
import io.toolisticon.aptk.tools.wrapper.AnnotationMirrorWrapper;
import io.toolisticon.aptk.tools.wrapper.AnnotationValueWrapper;
+import io.toolisticon.aptk.tools.wrapper.ExecutableElementWrapper;
import io.toolisticon.aptk.tools.wrapper.TypeElementWrapper;
+import io.toolisticon.aptk.tools.wrapper.VariableElementWrapper;
-import javax.lang.model.element.ExecutableElement;
-import java.util.List;
+import javax.lang.model.element.Modifier;
+import java.util.Arrays;
+import java.util.stream.Collectors;
public class ModelValidator {
+ private final VariableElementWrapper annotatedElement;
private final AnnotationMirrorWrapper validatorAnnotation;
- public ModelValidator(AnnotationMirrorWrapper validatorAnnotation) {
+ public ModelValidator(VariableElementWrapper annotatedElement, AnnotationMirrorWrapper validatorAnnotation) {
+ this.annotatedElement = annotatedElement;
this.validatorAnnotation = validatorAnnotation;
}
@@ -23,26 +28,61 @@ FluentApiValidatorWrapper getValidatorAnnotation() {
return FluentApiValidatorWrapper.wrap(this.validatorAnnotation.asElement().unwrap());
}
- // TODO NOT USED YET, MUST BE IMPLEMENTED
+
+ @DeclareCompilerMessage(code = "752", enumValueName = "ERROR_BROKEN_VALIDATOR_ATTRIBUTE_NAME_MISMATCH", message = "The configured validator ${0} seems to have a broken annotation attribute to validator constructor parameter mapping. Attributes names ${1} doesn't exist. Please fix validator implementation or don't use it.", processorClass = FluentApiProcessor.class)
+ @DeclareCompilerMessage(code = "753", enumValueName = "ERROR_BROKEN_VALIDATOR_CONSTRUCTOR_PARAMETER_MAPPING", message = "The configured validator ${0} seems to have a broken annotation attribute to validator constructor parameter mapping. Please fix validator implementation or don't use it.", processorClass = FluentApiProcessor.class)
+ @DeclareCompilerMessage(code = "754", enumValueName = "ERROR_BROKEN_VALIDATOR_MISSING_NOARG_CONSTRUCTOR", message = "The configured validator ${0} doesn't have a noarg constructor and therefore seems to be broken. Please fix validator implementation or don't use it.", processorClass = FluentApiProcessor.class)
+
public boolean validate() {
// must check if parameter types are assignable
FluentApiValidatorWrapper validatorMetaAnnotation = getValidatorAnnotation();
TypeMirrorWrapper validatorImplType = validatorMetaAnnotation.valueAsTypeMirrorWrapper();
- String[] parameterNames = validatorMetaAnnotation.parameterNames();
+ String[] attributeNamesToConstructorParameterMapping = validatorMetaAnnotation.attributeNamesToConstructorParameterMapping();
+
+ if (attributeNamesToConstructorParameterMapping.length > 0) {
+
+ // First check if annotation attribute Names are correct
+ String[] invalidNames = Arrays.stream(attributeNamesToConstructorParameterMapping).filter(e -> !this.validatorAnnotation.getAttribute(e).isPresent()).toArray(String[]::new);
+ if (invalidNames.length > 0) {
+ this.annotatedElement.compilerMessage(this.validatorAnnotation.unwrap()).asError().write(FluentApiProcessorCompilerMessages.ERROR_BROKEN_VALIDATOR_ATTRIBUTE_NAME_MISMATCH, this.validatorAnnotation.asElement().getSimpleName(), invalidNames);
+ }
+
+
+ // loop over constructors and find if one is matching
+ outer:
+ for (ExecutableElementWrapper constructor : validatorImplType.getTypeElement().get().getConstructors(Modifier.PUBLIC)) {
+
+ if (constructor.getParameters().size() != attributeNamesToConstructorParameterMapping.length) {
+ continue;
+ }
+
+ int i = 0;
+ for (String attributeName : attributeNamesToConstructorParameterMapping) {
+
+ TypeMirrorWrapper attribute = this.validatorAnnotation.getAttributeTypeMirror(attributeName).get();
- if (parameterNames.length > 0) {
+ if (!attribute.isAssignableTo(constructor.getParameters().get(i).asType())) {
+ continue outer;
+ }
- } else if (!validatorImplType.getTypeElement().isPresent()) {
- // couldn't get type element
+ // next
+ i = i + 1;
+ }
+
+ // if this is reached, the we have found a matching constructor
+ return true;
+ }
+
+ annotatedElement.compilerMessage(validatorAnnotation.unwrap()).asError().write(FluentApiProcessorCompilerMessages.ERROR_BROKEN_VALIDATOR_MISSING_NOARG_CONSTRUCTOR, validatorImplType.getSimpleName());
+ return false;
} else {
// must have a noarg constructor or just the default
TypeElementWrapper validatorImplTypeElement = validatorImplType.getTypeElement().get();
boolean hasNoargConstructor = validatorImplTypeElement.filterEnclosedElements().applyFilter(AptkCoreMatchers.IS_CONSTRUCTOR).applyFilter(AptkCoreMatchers.HAS_NO_PARAMETERS).getResult().size() == 1;
boolean hasJustDefaultConstructor = validatorImplTypeElement.filterEnclosedElements().applyFilter(AptkCoreMatchers.IS_CONSTRUCTOR).hasSize(0);
- if (! ( hasNoargConstructor || hasJustDefaultConstructor)) {
- //ToolingProvider.getTooling().getMessager().
- //this.validatorAnnotation.
+ if (!(hasNoargConstructor || hasJustDefaultConstructor)) {
+ annotatedElement.compilerMessage(validatorAnnotation.unwrap()).asError().write(FluentApiProcessorCompilerMessages.ERROR_BROKEN_VALIDATOR_MISSING_NOARG_CONSTRUCTOR, validatorImplTypeElement.getSimpleName());
}
}
@@ -55,7 +95,7 @@ public String validatorExpression() {
stringBuilder.append("new ").append(getValidatorAnnotation().valueAsFqn()).append("(");
boolean isFirst = true;
- for (String parameterName : getValidatorAnnotation().parameterNames()) {
+ for (String attributeName : getValidatorAnnotation().attributeNamesToConstructorParameterMapping()) {
// add separator
if (!isFirst) {
@@ -64,27 +104,7 @@ public String validatorExpression() {
isFirst = false;
}
- AnnotationValueWrapper attribute = validatorAnnotation.getAttributeWithDefault(parameterName);
-
- if (attribute.isString()) {
- stringBuilder.append("\"").append(attribute.getStringValue()).append("\"");
- } else if (attribute.isClass()) {
- stringBuilder.append(attribute.getClassValue().getQualifiedName()).append(".class");
- } else if (attribute.isInteger()) {
- stringBuilder.append(attribute.getIntegerValue());
- } else if (attribute.isLong()) {
- stringBuilder.append(attribute.getLongValue()).append("L");
- } else if (attribute.isBoolean()) {
- stringBuilder.append(attribute.getBooleanValue());
- } else if (attribute.isFloat()) {
- stringBuilder.append(attribute.getFloatValue()).append("f");
- } else if (attribute.isDouble()) {
- stringBuilder.append(attribute.getFloatValue());
- } else if (attribute.isEnum()) {
- stringBuilder.append(TypeElementWrapper.toTypeElement(attribute.getEnumValue().getEnclosingElement().get()).getQualifiedName());
- stringBuilder.append(".");
- stringBuilder.append(attribute.getEnumValue().getSimpleName());
- }
+ stringBuilder.append(getValidatorExpressionAttributeValueStringRepresentation(validatorAnnotation.getAttributeWithDefault(attributeName), validatorAnnotation.getAttributeTypeMirror(attributeName).get()));
}
@@ -92,6 +112,33 @@ public String validatorExpression() {
return stringBuilder.toString();
}
+
+ String getValidatorExpressionAttributeValueStringRepresentation(AnnotationValueWrapper annotationValueWrapper, TypeMirrorWrapper annotationAttributeTypeMirror) {
+
+ if (annotationValueWrapper.isArray()) {
+ return annotationValueWrapper.getArrayValue().stream().map(e -> getValidatorExpressionAttributeValueStringRepresentation(e, annotationAttributeTypeMirror.getWrappedComponentType())).collect(Collectors.joining(", ", "new " + annotationAttributeTypeMirror.getWrappedComponentType().getQualifiedName() + "[]{", "}"));
+ } else if (annotationValueWrapper.isString()) {
+ return "\"" + annotationValueWrapper.getStringValue() + "\"";
+ } else if (annotationValueWrapper.isClass()) {
+ return annotationValueWrapper.getClassValue().getQualifiedName() + ".class";
+ } else if (annotationValueWrapper.isInteger()) {
+ return annotationValueWrapper.getIntegerValue().toString();
+ } else if (annotationValueWrapper.isLong()) {
+ return annotationValueWrapper.getLongValue() + "L";
+ } else if (annotationValueWrapper.isBoolean()) {
+ return annotationValueWrapper.getBooleanValue().toString();
+ } else if (annotationValueWrapper.isFloat()) {
+ return annotationValueWrapper.getFloatValue() + "f";
+ } else if (annotationValueWrapper.isDouble()) {
+ return annotationValueWrapper.getDoubleValue().toString();
+ } else if (annotationValueWrapper.isEnum()) {
+ return TypeElementWrapper.toTypeElement(annotationValueWrapper.getEnumValue().getEnclosingElement().get()).getQualifiedName() + "." + annotationValueWrapper.getEnumValue().getSimpleName();
+ } else {
+ throw new IllegalStateException("Got unsupported annotation attribute type : USUALLY THIS CANNOT HAPPEN.");
+ }
+
+ }
+
public String getValidatorSummary() {
return validatorAnnotation.getStringRepresentation();
}
diff --git a/fluapigen-processor/src/test/java/io/toolisticon/fluapigen/processor/ValidatorTest.java b/fluapigen-processor/src/test/java/io/toolisticon/fluapigen/processor/ValidatorTest.java
index 71a37ea..b11a1f9 100644
--- a/fluapigen-processor/src/test/java/io/toolisticon/fluapigen/processor/ValidatorTest.java
+++ b/fluapigen-processor/src/test/java/io/toolisticon/fluapigen/processor/ValidatorTest.java
@@ -2,17 +2,24 @@
import io.toolisticon.aptk.common.ToolingProvider;
import io.toolisticon.aptk.tools.MessagerUtils;
+import io.toolisticon.aptk.tools.wrapper.AnnotationMirrorWrapper;
import io.toolisticon.aptk.tools.wrapper.VariableElementWrapper;
import io.toolisticon.cute.CompileTestBuilder;
import io.toolisticon.cute.PassIn;
import io.toolisticon.cute.UnitTest;
+import io.toolisticon.fluapigen.validation.api.FluentApiValidator;
import io.toolisticon.fluapigen.validation.api.Matches;
+import io.toolisticon.fluapigen.validation.api.Validator;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import javax.lang.model.element.VariableElement;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
public class ValidatorTest {
@@ -41,7 +48,7 @@ public void testMatchesValidator() {
try {
ToolingProvider.setTooling(processingEnvironment);
- ModelValidator unit = new ModelValidator(VariableElementWrapper.wrap(element).getAnnotationMirror(Matches.class).get());
+ ModelValidator unit = new ModelValidator(VariableElementWrapper.wrap(element), VariableElementWrapper.wrap(element).getAnnotationMirror(Matches.class).get());
MatcherAssert.assertThat(unit.validatorExpression(), Matchers.is("new io.toolisticon.fluapigen.validation.api.Matches.ValidatorImpl(\"aaa.*\")"));
} finally {
@@ -60,4 +67,128 @@ public void compilationTestValidator() {
}
+ @Target(ElementType.PARAMETER)
+ @Retention(RetentionPolicy.RUNTIME)
+ @FluentApiValidator(value = AttributeValueStringRepresentationTest.MyValidator.class, attributeNamesToConstructorParameterMapping = {"longValue", "intValue", "floatValue", "doubleValue", "booleanValue", "enumValue", "stringValue", "arrayValue", "typeValue"})
+ public @interface AttributeValueStringRepresentationTest {
+ long longValue();
+
+ int intValue();
+
+ float floatValue();
+
+ double doubleValue();
+
+ boolean booleanValue();
+
+ TestEnum enumValue();
+
+ String stringValue();
+
+ String[] arrayValue();
+
+ Class> typeValue();
+
+ class MyValidator implements Validator {
+
+ final long longValue;
+ final int intValue;
+ final float floatValue;
+ final double doubleValue;
+
+ final boolean booleanValue;
+
+ final TestEnum enumValue;
+
+ final String stringValue;
+
+ final String[] arrayValue;
+
+ final Class> typeValue;
+
+ public MyValidator(long longValue, int intValue, float floatValue, double doubleValue, boolean booleanValue, TestEnum enumValue, String stringValue, String[] arrayValue, Class> typeValue) {
+ this.longValue = longValue;
+ this.intValue = intValue;
+ this.floatValue = floatValue;
+ this.doubleValue = doubleValue;
+ this.booleanValue = booleanValue;
+ this.enumValue = enumValue;
+ this.stringValue = stringValue;
+ this.arrayValue = arrayValue;
+ this.typeValue = typeValue;
+ }
+
+ @Override
+ public boolean validate(String obj) {
+ return false;
+ }
+ }
+
+ }
+
+
+ public enum TestEnum {
+ TEST;
+ }
+
+ public interface TestCase {
+
+
+ void testCase(@PassIn @AttributeValueStringRepresentationTest(longValue = 1L, intValue = 2, floatValue = 3f, doubleValue = 4.0, booleanValue = true, enumValue = TestEnum.TEST, stringValue = "TEST", typeValue = String.class, arrayValue = {"ARRAY1", "ARRAY2"})
+ String parameter);
+
+ }
+
+ @Test
+ public void test_getValidatorExpressionAttributeValueStringRepresentation() {
+ unitTestBuilder.defineTestWithPassedInElement(TestCase.class, (UnitTest) (processingEnvironment, element) -> {
+
+ try {
+ ToolingProvider.setTooling(processingEnvironment);
+
+ AnnotationMirrorWrapper annotationMirrorWrapper = AnnotationMirrorWrapper.get(element, AttributeValueStringRepresentationTest.class).get();
+
+ ModelValidator unit = new ModelValidator(VariableElementWrapper.wrap(element), annotationMirrorWrapper);
+
+ MatcherAssert.assertThat(unit.getValidatorExpressionAttributeValueStringRepresentation(annotationMirrorWrapper.getAttribute("longValue").get(), annotationMirrorWrapper.getAttributeTypeMirror("longValue").get()), Matchers.is("1L"));
+ MatcherAssert.assertThat(unit.getValidatorExpressionAttributeValueStringRepresentation(annotationMirrorWrapper.getAttribute("intValue").get(), annotationMirrorWrapper.getAttributeTypeMirror("intValue").get()), Matchers.is("2"));
+ MatcherAssert.assertThat(unit.getValidatorExpressionAttributeValueStringRepresentation(annotationMirrorWrapper.getAttribute("floatValue").get(), annotationMirrorWrapper.getAttributeTypeMirror("floatValue").get()), Matchers.is("3.0f"));
+ MatcherAssert.assertThat(unit.getValidatorExpressionAttributeValueStringRepresentation(annotationMirrorWrapper.getAttribute("doubleValue").get(), annotationMirrorWrapper.getAttributeTypeMirror("doubleValue").get()), Matchers.is("4.0"));
+ MatcherAssert.assertThat(unit.getValidatorExpressionAttributeValueStringRepresentation(annotationMirrorWrapper.getAttribute("booleanValue").get(), annotationMirrorWrapper.getAttributeTypeMirror("booleanValue").get()), Matchers.is("true"));
+ MatcherAssert.assertThat(unit.getValidatorExpressionAttributeValueStringRepresentation(annotationMirrorWrapper.getAttribute("enumValue").get(), annotationMirrorWrapper.getAttributeTypeMirror("enumValue").get()), Matchers.is("io.toolisticon.fluapigen.processor.ValidatorTest.TestEnum.TEST"));
+ MatcherAssert.assertThat(unit.getValidatorExpressionAttributeValueStringRepresentation(annotationMirrorWrapper.getAttribute("stringValue").get(), annotationMirrorWrapper.getAttributeTypeMirror("stringValue").get()), Matchers.is("\"TEST\""));
+ MatcherAssert.assertThat(unit.getValidatorExpressionAttributeValueStringRepresentation(annotationMirrorWrapper.getAttribute("typeValue").get(), annotationMirrorWrapper.getAttributeTypeMirror("typeValue").get()), Matchers.is("java.lang.String.class"));
+ MatcherAssert.assertThat(unit.getValidatorExpressionAttributeValueStringRepresentation(annotationMirrorWrapper.getAttribute("arrayValue").get(), annotationMirrorWrapper.getAttributeTypeMirror("arrayValue").get()), Matchers.is("new java.lang.String[]{\"ARRAY1\", \"ARRAY2\"}"));
+
+
+ } finally {
+ ToolingProvider.clearTooling();
+ }
+
+ }).compilationShouldSucceed()
+ .executeTest();
+ }
+
+ @Test
+ public void test_validate() {
+ unitTestBuilder.defineTestWithPassedInElement(TestCase.class, (UnitTest) (processingEnvironment, element) -> {
+
+ try {
+ ToolingProvider.setTooling(processingEnvironment);
+
+ AnnotationMirrorWrapper annotationMirrorWrapper = AnnotationMirrorWrapper.get(element, AttributeValueStringRepresentationTest.class).get();
+
+ ModelValidator unit = new ModelValidator(VariableElementWrapper.wrap(element), annotationMirrorWrapper);
+
+ MatcherAssert.assertThat("Validation must be true", unit.validate());
+
+ } finally {
+ ToolingProvider.clearTooling();
+ }
+
+ }).compilationShouldSucceed()
+ .executeTest();
+ }
+
+
}
diff --git a/fluapigen-validation-api/pom.xml b/fluapigen-validation-api/pom.xml
index e2cb556..366cffe 100644
--- a/fluapigen-validation-api/pom.xml
+++ b/fluapigen-validation-api/pom.xml
@@ -9,7 +9,7 @@
io.toolisticon.fluapigen
fluapigen
- 0.8.0
+ 0.8.1
fluapigen-validation-api
diff --git a/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/FluentApiValidator.java b/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/FluentApiValidator.java
index db49a58..7a2dbaf 100644
--- a/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/FluentApiValidator.java
+++ b/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/FluentApiValidator.java
@@ -22,6 +22,6 @@
* Used to map parameters to validator constructor.
* @return the parameter names
*/
- String[] parameterNames () default {};
+ String[] attributeNamesToConstructorParameterMapping() default {};
}
diff --git a/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/Matches.java b/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/Matches.java
index 1a83227..da4b8b6 100644
--- a/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/Matches.java
+++ b/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/Matches.java
@@ -3,7 +3,7 @@
import java.util.regex.Pattern;
-@FluentApiValidator(value = Matches.ValidatorImpl.class, parameterNames = {"value"})
+@FluentApiValidator(value = Matches.ValidatorImpl.class, attributeNamesToConstructorParameterMapping = {"value"})
public @interface Matches {
String value();
diff --git a/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/MaxLength.java b/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/MaxLength.java
index cc0eb18..c9e240c 100644
--- a/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/MaxLength.java
+++ b/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/MaxLength.java
@@ -4,7 +4,7 @@
import java.util.Collection;
-@FluentApiValidator(value = MaxLength.ValidatorImpl.class, parameterNames = {"value"})
+@FluentApiValidator(value = MaxLength.ValidatorImpl.class, attributeNamesToConstructorParameterMapping = {"value"})
public @interface MaxLength {
int value();
diff --git a/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/MinLength.java b/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/MinLength.java
index f5bdbd6..f5a5889 100644
--- a/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/MinLength.java
+++ b/fluapigen-validation-api/src/main/java/io/toolisticon/fluapigen/validation/api/MinLength.java
@@ -4,7 +4,7 @@
import java.util.Collection;
-@FluentApiValidator(value = MinLength.ValidatorImpl.class, parameterNames = {"value"})
+@FluentApiValidator(value = MinLength.ValidatorImpl.class, attributeNamesToConstructorParameterMapping = {"value"})
public @interface MinLength {
int value();
diff --git a/pom.xml b/pom.xml
index c69e883..34bd481 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
io.toolisticon.fluapigen
fluapigen
- 0.8.0
+ 0.8.1
pom
fluapigen
@@ -73,12 +73,12 @@
0.12.1
0.11.0
- 0.22.7
+ 0.22.8
4.13.2
2.2
- 5.2.0
+ 5.4.0
https://oss.sonatype.org/content/repositories/snapshots/
@@ -89,28 +89,28 @@
1.18
3.5.0
3.3.0
- 3.2.0
+ 3.3.2
3.11.0
- 3.5.0
- 3.1.0
- 3.2.1
- 3.0.0
- 3.0.1
- 3.3.0
- 3.1.0
+ 3.6.1
+ 3.1.1
+ 3.3.0
+ 3.2.2
+ 3.1.0
+ 3.4.0
+ 3.1.1
0.8.8
3.3.0
3.5.0
2.2.4
- 2.5.3
- 3.3.0
- 3.4.1
- 3.2.1
+ 3.0.1
+ 3.3.1
+ 3.5.1
+ 3.3.0
3.12.1
- 3.0.0
+ 3.2.2
2.5
- 1.19.0
+ 1.20.0
1.6.13
@@ -332,7 +332,7 @@
maven-project-info-reports-plugin
- 3.4.2
+ 3.4.5
false
@@ -515,7 +515,7 @@
org.apache.maven.plugins
maven-checkstyle-plugin
- 3.2.1
+ 3.3.1
validate
@@ -538,7 +538,7 @@
org.apache.maven.plugins
maven-pmd-plugin
- 3.20.0
+ 3.21.2