Skip to content

Commit

Permalink
[#89] Added possibility to add NotNull at method, fluent interface an…
Browse files Browse the repository at this point in the history
…d wrapping FluentApi Type. Additionally added logic to overrule Validators i.e. with Nullable for NotNull
  • Loading branch information
tobiasstamann committed Feb 5, 2024
1 parent a88ba41 commit 6398bd5
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.toolisticon.fluapigen.validation.api.Matches;
import io.toolisticon.fluapigen.validation.api.MaxLength;
import io.toolisticon.fluapigen.validation.api.NotNull;
import io.toolisticon.fluapigen.validation.api.Nullable;

@FluentApi("ValidatorExampleStarter")
public class ValidatorExample {
Expand All @@ -26,10 +27,18 @@ interface MyBackingBean {
// Fluent Api interfaces
@FluentApiInterface(MyBackingBean.class)
@FluentApiRoot
@Nullable
public interface MyRootInterface {

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

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

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


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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.toolisticon.fluapigen.integrationtest;

import io.toolisticon.fluapigen.validation.api.ValidatorException;
import org.junit.Test;

public class ValidatorExampleTest {
Expand All @@ -15,7 +16,14 @@ public void testValidator_failingValidation() {
public void testValidator() {

ValidatorExampleStarter.setName("aaaBDSXS").myCommand();
ValidatorExampleStarter.setNameWithNullableOnParameter(null).myCommand();
ValidatorExampleStarter.setNameWithNullableOnMethod("aaaBDSXS").myCommand();

}

@Test(expected = ValidatorException.class)
public void testOverruledNotNullAtParameter() {
ValidatorExampleStarter.setNameWithNullableOnMethod(null).myCommand();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
import io.toolisticon.aptk.compilermessage.api.DeclareCompilerMessage;
import io.toolisticon.aptk.tools.InterfaceUtils;
import io.toolisticon.aptk.tools.TypeMirrorWrapper;
import io.toolisticon.aptk.tools.wrapper.ElementWrapper;
import io.toolisticon.aptk.tools.wrapper.VariableElementWrapper;
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 javax.lang.model.element.Element;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -83,7 +87,29 @@ public boolean hasValidators() {
}

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

// First get all enclosing elements - order is from element to top level parent
List<ElementWrapper<Element>> enclosingElements = this.parameterElement.getAllEnclosingElements(true);

// now map it to ModelValidators
List<ModelValidator> allValidatorsWithoutOverwrites = enclosingElements.stream()
.flatMap(
e -> e.getAnnotations().stream()
.filter(f -> f.asElement().hasAnnotation(FluentApiValidator.class))
.map(f -> new ModelValidator(this.parameterElement, f))
).collect(Collectors.toList());

// Must remove overwritten validators - add them one by one from lowest to highest level and check if the element to add is already overruled by thos in list.
List<ModelValidator> result = new ArrayList();

for (ModelValidator nextValidator : allValidatorsWithoutOverwrites) {
if (!nextValidator.isOverruledBy(result)) {
result.add(nextValidator);
}
}

return result;

}

public String getParameterName() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import javax.lang.model.element.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class ModelValidator {
Expand All @@ -28,6 +29,23 @@ FluentApiValidatorWrapper getValidatorAnnotation() {
return FluentApiValidatorWrapper.wrap(this.validatorAnnotation.asElement().unwrap());
}

// visible for testing
AnnotationMirrorWrapper getAnnotationMirrorWrapper() {
return validatorAnnotation;
}

public boolean isOverruledBy (List<ModelValidator> existingLowestLevelValidators) {
for (ModelValidator existinModelValidator : existingLowestLevelValidators) {

for (String overruledAnnotation : existinModelValidator.getValidatorAnnotation().overwritesAsFqn()) {
if (overruledAnnotation.equals(validatorAnnotation.asTypeMirror().getQualifiedName())) {
return true;
}
}

}
return false;
}

@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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package io.toolisticon.fluapigen.processor;

import io.toolisticon.aptk.cute.APTKUnitTestProcessor;
import io.toolisticon.aptk.tools.wrapper.VariableElementWrapper;
import io.toolisticon.cute.Cute;
import io.toolisticon.cute.PassIn;
import io.toolisticon.fluapigen.validation.api.Matches;
import io.toolisticon.fluapigen.validation.api.NotNull;
import io.toolisticon.fluapigen.validation.api.Nullable;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.Test;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.VariableElement;
import java.util.stream.Collectors;

/**
* Unit test for {@link ModelInterfaceMethodParameter}.
*/
public class ModelInterfaceMethodParameterTest {

interface TestClassWithoutValidators {

public void testMethod(@PassIn String parameter);

}


@Test
public void test_getValidators_withoutValidatorsPresent() {
Cute.unitTest().when().passInElement().<VariableElement>fromClass(TestClassWithoutValidators.class)
.intoUnitTest(new APTKUnitTestProcessor<VariableElement>() {
@Override
public void aptkUnitTest(ProcessingEnvironment processingEnvironment, VariableElement variableElement) {

ModelInterfaceMethodParameter unit = new ModelInterfaceMethodParameter(VariableElementWrapper.wrap(variableElement), null, null);

MatcherAssert.assertThat("Expect to find no validators", !unit.hasValidators());

}
}).executeTest();
}

@NotNull
interface TestClassWithValidators {

public void testMethod(@PassIn @Matches(".*[@].*") String parameter);

}

@Test
public void test_getValidators_withValidatorsPresent() {
Cute.unitTest().when().passInElement().<VariableElement>fromClass(TestClassWithValidators.class)
.intoUnitTest(new APTKUnitTestProcessor<VariableElement>() {
@Override
public void aptkUnitTest(ProcessingEnvironment processingEnvironment, VariableElement variableElement) {

ModelInterfaceMethodParameter unit = new ModelInterfaceMethodParameter(VariableElementWrapper.wrap(variableElement), null, null);

MatcherAssert.assertThat("Expect to find validators", unit.hasValidators());
MatcherAssert.assertThat("Expect to find 2 validators", unit.getValidators().size() == 2);
MatcherAssert.assertThat(unit.getValidators().stream().map(e -> e.getAnnotationMirrorWrapper().asTypeMirror().getQualifiedName()).collect(Collectors.toList()), Matchers.containsInAnyOrder(NotNull.class.getCanonicalName(), Matches.class.getCanonicalName()));


}
}).executeTest();
}

@NotNull
interface TestClassWithOverruledValidators {

void testMethod(@PassIn @Nullable @Matches(".*[@].*") String parameter);

}

@Test
public void test_getValidators_withOverruledValidatorsPresent() {
Cute.unitTest().when().passInElement().<VariableElement>fromClass(TestClassWithOverruledValidators.class)
.intoUnitTest(new APTKUnitTestProcessor<VariableElement>() {
@Override
public void aptkUnitTest(ProcessingEnvironment processingEnvironment, VariableElement variableElement) {

ModelInterfaceMethodParameter unit = new ModelInterfaceMethodParameter(VariableElementWrapper.wrap(variableElement), null, null);

MatcherAssert.assertThat("Expect to find validators", unit.hasValidators());
MatcherAssert.assertThat("Expect to find 2 validators", unit.getValidators().size() == 2);
MatcherAssert.assertThat(unit.getValidators().stream().map(e -> e.getAnnotationMirrorWrapper().asTypeMirror().getQualifiedName()).collect(Collectors.toList()), Matchers.containsInAnyOrder(Nullable.class.getCanonicalName(), Matches.class.getCanonicalName()));


}
}).executeTest();
}

@Nullable
interface TestClassWithOverruledValidators2 {

void testMethod(@PassIn @NotNull @Matches(".*[@].*") String parameter);

}

@Test
public void test_getValidators_withOverruledValidatorsPresent2() {
Cute.unitTest().when().passInElement().<VariableElement>fromClass(TestClassWithOverruledValidators2.class)
.intoUnitTest(new APTKUnitTestProcessor<VariableElement>() {
@Override
public void aptkUnitTest(ProcessingEnvironment processingEnvironment, VariableElement variableElement) {

ModelInterfaceMethodParameter unit = new ModelInterfaceMethodParameter(VariableElementWrapper.wrap(variableElement), null, null);

MatcherAssert.assertThat("Expect to find validators", unit.hasValidators());
MatcherAssert.assertThat("Expect to find 2 validators", unit.getValidators().size() == 2);
MatcherAssert.assertThat(unit.getValidators().stream().map(e -> e.getAnnotationMirrorWrapper().asTypeMirror().getQualifiedName()).collect(Collectors.toList()), Matchers.containsInAnyOrder(NotNull.class.getCanonicalName(), Matches.class.getCanonicalName()));


}
}).executeTest();
}

}

0 comments on commit 6398bd5

Please sign in to comment.