Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiasstamann committed Nov 6, 2023
2 parents 7a85b36 + 85778ea commit 08fb473
Show file tree
Hide file tree
Showing 24 changed files with 615 additions and 18 deletions.
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,54 @@ public interface MyFluentInterface {

}
```

### Using validators at fluent interface method parameters
FluApiGen provides some basic validators that can be applied to the fluent method parameters at runtime.

- _Matches_ : Regular Expression validator for Strings
- _MinLength / MaxLength_ : Checks length of Strings or size of Arrays or Collections
- _NotEmpty_ : Checks if String, array and Collection are not empty
- _NotNull_ : Checks if passed argument isn't null

To be able to use validators, the _fluapigen_validation_api_ library must be linked at compile and runtime.

````java
MyRootInterface setName(@Matches("Max.*") @FluentApiBackingBeanMapping("name") String name);
````

In this example the fluent api would throw a _ValidatorException_ if the name doesn't start with "Max".

It's possible to use multiple validators on a parameter and even to provide custom validators:

````java
@FluentApiValidator(value = Matches.ValidatorImpl.class, parameterNames = {"value"})
public @interface Matches {

String value();

class ValidatorImpl implements Validator<String> {

private final String regularExpression;

public ValidatorImpl(String regularExpression) {
this.regularExpression = regularExpression;
}

@Override
public boolean validate(String obj) {
return Pattern.compile(regularExpression).matcher(obj).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.
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*

### Javas default methods in fluent api and backing bean in interfaces
Default methods will be ignored during processing of fluent api and backing bean interfaces and can be used for different tasks:

Expand Down
11 changes: 9 additions & 2 deletions fluapigen-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>io.toolisticon.fluapigen</groupId>
<artifactId>fluapigen</artifactId>
<version>0.7.0</version>
<version>0.8.0</version>
</parent>

<name>fluapigen-api</name>
Expand All @@ -25,7 +25,14 @@

</plugins>

</build>


</build>
<dependencies>
<dependency>
<groupId>io.toolisticon.fluapigen</groupId>
<artifactId>fluapigen-validation-api</artifactId>
</dependency>
</dependencies>

</project>
2 changes: 1 addition & 1 deletion fluapigen-example/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>io.toolisticon.fluapigen</groupId>
<artifactId>fluapigen</artifactId>
<version>0.7.0</version>
<version>0.8.0</version>
</parent>

<name>fluapigen-example</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.toolisticon.fluapigen.api.FluentApiInterface;
import io.toolisticon.fluapigen.api.FluentApiRoot;


@FluentApi("ExampleFluentApiStarter")
public class ExampleFluentApi {

Expand Down
13 changes: 11 additions & 2 deletions fluapigen-integrationTest/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>io.toolisticon.fluapigen</groupId>
<artifactId>fluapigen</artifactId>
<version>0.7.0</version>
<version>0.8.0</version>
</parent>

<name>fluapigen-integrationTest</name>
Expand All @@ -29,7 +29,11 @@
<artifactId>fluapigen-processor</artifactId>
<version>${project.version}</version>
</path>

<path>
<groupId>io.toolisticon.fluapigen</groupId>
<artifactId>fluapigen-api</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>

Expand Down Expand Up @@ -97,6 +101,11 @@
<artifactId>fluapigen-api</artifactId>
</dependency>

<dependency>
<groupId>io.toolisticon.fluapigen</groupId>
<artifactId>fluapigen-validation-api</artifactId>
</dependency>

<dependency>
<groupId>io.toolisticon.cute</groupId>
<artifactId>cute</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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.FluentApiInterface;
import io.toolisticon.fluapigen.api.FluentApiRoot;
import io.toolisticon.fluapigen.validation.api.Matches;
import io.toolisticon.fluapigen.validation.api.MaxLength;
import io.toolisticon.fluapigen.validation.api.NotNull;

@FluentApi("ValidatorExampleStarter")
public class ValidatorExample {

// Backing Bean Interface
@FluentApiBackingBean
interface MyBackingBean {

@FluentApiBackingBeanField("name")
String getName();

}

// Fluent Api interfaces
@FluentApiInterface(MyBackingBean.class)
@FluentApiRoot
public interface MyRootInterface {

MyRootInterface setName(@NotNull @MaxLength(8) @Matches("aaa.*") @FluentApiBackingBeanMapping("name") String name);

@FluentApiCommand(MyCommand.class)
void myCommand();

}

// Commands
@FluentApiCommand
static class MyCommand {
static void myCommand(MyBackingBean backingBean) {
System.out.println(backingBean.getName());
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.toolisticon.fluapigen.integrationtest;

import org.junit.Test;

public class ValidatorExampleTest {

@Test(expected = RuntimeException.class)
public void testValidator_failingValidation() {

ValidatorExampleStarter.setName("bbbadadadsd").myCommand();

}

@Test()
public void testValidator() {

ValidatorExampleStarter.setName("aaaBDSXS").myCommand();

}

}
2 changes: 1 addition & 1 deletion fluapigen-processor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<parent>
<groupId>io.toolisticon.fluapigen</groupId>
<artifactId>fluapigen</artifactId>
<version>0.7.0</version>
<version>0.8.0</version>
</parent>

<name>fluapigen-processor</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.toolisticon.fluapigen.api.FluentApiBackingBeanMapping;
import io.toolisticon.fluapigen.api.FluentApiConverter;
import io.toolisticon.fluapigen.api.TargetBackingBean;
import io.toolisticon.fluapigen.validation.api.FluentApiValidator;

import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -44,7 +45,7 @@ public Optional<ModelBackingBeanField> getBackingBeanField() {

switch (fluentApiBackingBeanMapping.target()) {
case THIS: {
returnValue = backingBeanModel.getFieldById(fluentApiBackingBeanMapping.value());
returnValue = backingBeanModel.getFieldById(fluentApiBackingBeanMapping.value());
break;
}
case NEXT: {
Expand All @@ -66,18 +67,26 @@ public FluentApiBackingBeanMappingWrapper getFluentApiBackingBeanMapping() {
return fluentApiBackingBeanMapping;
}

public boolean hasConverter () {
public boolean hasConverter() {
return !fluentApiBackingBeanMapping.converterIsDefaultValue();
//&& !getFluentApiBackingBeanMapping().converterAsFqn().equals(FluentApiConverter.NoConversion.class.getCanonicalName()
//&& !getFluentApiBackingBeanMapping().converterAsFqn().equals(FluentApiConverter.NoConversion.class.getCanonicalName()
}


public boolean hasValidators() {
return getValidators().size() > 0;
}

public List<ModelValidator> getValidators() {
return this.parameterElement.getAnnotations().stream().filter(e -> e.asElement().hasAnnotation(FluentApiValidator.class)).map(ModelValidator::new).collect(Collectors.toList());
}

public String getParameterName() {
return parameterName;
}

String addConverterIfNeeded(String parameterName) {
return !hasConverter() ? parameterName : "new " + getFluentApiBackingBeanMapping().converterAsTypeMirrorWrapper().getQualifiedName() + "().convert(" + parameterName +")";
return !hasConverter() ? parameterName : "new " + getFluentApiBackingBeanMapping().converterAsTypeMirrorWrapper().getQualifiedName() + "().convert(" + parameterName + ")";
}


Expand Down Expand Up @@ -170,7 +179,7 @@ else if (backBeanField.get().isArray()) {
}

// just parameter assignment
return " = " + addConverterIfNeeded(getParameterName()) +";";
return " = " + addConverterIfNeeded(getParameterName()) + ";";
}

}
Expand All @@ -188,7 +197,7 @@ private void validateConverter(ModelBackingBeanField backBeanField) {

if (!sourceType.isAssignableTo(converterTypeArguments.get(0))
|| !converterTypeArguments.get(1).isAssignableTo(targetType)) {
throw new IncompatibleConverterException(fluentApiBackingBeanMapping,sourceType,targetType,converter,converterTypeArguments);
throw new IncompatibleConverterException(fluentApiBackingBeanMapping, sourceType, targetType, converter, converterTypeArguments);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package io.toolisticon.fluapigen.processor;

import io.toolisticon.aptk.common.ToolingProvider;
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.TypeElementWrapper;

import javax.lang.model.element.ExecutableElement;
import java.util.List;

public class ModelValidator {


private final AnnotationMirrorWrapper validatorAnnotation;

public ModelValidator(AnnotationMirrorWrapper validatorAnnotation) {
this.validatorAnnotation = validatorAnnotation;
}

FluentApiValidatorWrapper getValidatorAnnotation() {
return FluentApiValidatorWrapper.wrap(this.validatorAnnotation.asElement().unwrap());
}

// TODO NOT USED YET, MUST BE IMPLEMENTED
public boolean validate() {
// must check if parameter types are assignable
FluentApiValidatorWrapper validatorMetaAnnotation = getValidatorAnnotation();
TypeMirrorWrapper validatorImplType = validatorMetaAnnotation.valueAsTypeMirrorWrapper();
String[] parameterNames = validatorMetaAnnotation.parameterNames();

if (parameterNames.length > 0) {

} else if (!validatorImplType.getTypeElement().isPresent()) {
// couldn't get type element
} 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.
}
}

return true;
}


public String validatorExpression() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("new ").append(getValidatorAnnotation().valueAsFqn()).append("(");

boolean isFirst = true;
for (String parameterName : getValidatorAnnotation().parameterNames()) {

// add separator
if (!isFirst) {
stringBuilder.append(", ");
} else {
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(")");
return stringBuilder.toString();
}

public String getValidatorSummary() {
return validatorAnnotation.getStringRepresentation();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
FluentApiImplicitValue.class,
FluentApiBackingBeanMapping.class,
FluentApiParentBackingBeanMapping.class,
FluentApiInlineBackingBeanMapping.class
FluentApiInlineBackingBeanMapping.class,
FluentApiValidator.class
})
package io.toolisticon.fluapigen.processor;

Expand All @@ -24,4 +25,5 @@
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.FluentApiParentBackingBeanMapping;
import io.toolisticon.fluapigen.validation.api.FluentApiValidator;
Loading

0 comments on commit 08fb473

Please sign in to comment.