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