Skip to content

Commit

Permalink
Issue #331 - Setup first detection of cycle
Browse files Browse the repository at this point in the history
  • Loading branch information
boretti committed May 31, 2020
1 parent 0b7fe07 commit 07423ee
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 16 deletions.
Expand Up @@ -30,7 +30,8 @@ public interface Matchable {

default long getCompatibility() {
// 0x01 : withSameValue also provides a version to ignore fields
return 0x01;
// 0x02 : withSameValue also use a list of parent to detect cycle
return 0x03;
}

default String getMethodNameDSLWithSameValue() {
Expand All @@ -49,6 +50,10 @@ default boolean supportIgnore() {
return (getCompatibility() & 0x01) == 0x01;
}

default boolean supportCycleDetectionV1() {
return (getCompatibility() & 0x02) == 0x02;
}

static Matchable of(String fullName, String methodName, String interfaceName, boolean hasWithSameValue,
long compatibility) {
return new Matchable() {
Expand Down
Expand Up @@ -23,6 +23,8 @@
import static java.lang.String.format;
import static java.util.stream.Collectors.joining;

import java.util.Optional;

import javax.lang.model.element.Element;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
Expand Down Expand Up @@ -65,8 +67,7 @@ public String getMatcherForField() {
}

public String getFieldCopy(String lhs, String rhs, String ignore) {
return mirror.getMatchable(containingElementMirror.getRoundMirror())
.filter(a -> mirror.getFieldTypeAsTypeElement().getTypeParameters().isEmpty())
return getTargetAsMatchable().filter(a -> mirror.getFieldTypeAsTypeElement().getTypeParameters().isEmpty())
.filter(Matchable::hasWithSameValue)
.map(p -> format(
"%1$s.%6$s(%2$s.%3$s == null ? org.hamcrest.Matchers.nullValue() : %4$s(%2$s.%3$s%5$s));", lhs,
Expand Down Expand Up @@ -120,6 +121,10 @@ public FieldDescriptionMirror getMirror() {
return mirror;
}

public Optional<Matchable> getTargetAsMatchable() {
return mirror.getMatchable(containingElementMirror.getRoundMirror());
}

public String getGeneric() {
return generic;
}
Expand Down
Expand Up @@ -18,6 +18,7 @@
import javax.lang.model.element.AnnotationValue;
import javax.tools.Diagnostic.Kind;

import ch.powerunit.extensions.matchers.provideprocessor.Matchable;
import ch.powerunit.extensions.matchers.provideprocessor.ProvidesMatchersAnnotatedElementMirror;
import ch.powerunit.extensions.matchers.provideprocessor.dsl.DSLMethod;
import ch.powerunit.extensions.matchers.provideprocessor.fields.AbstractFieldDescription;
Expand All @@ -27,8 +28,8 @@ public final class ProvidesMatchersWithSameValueHelper {
private ProvidesMatchersWithSameValueHelper() {
}

private static DSLMethod generateWithSameValueWithParentMatcherIgnore(ProvidesMatchersAnnotatedElementMirror target,
boolean hasSuper) {
private static DSLMethod generateWithSameValueWithParentMatcherIgnoreAndCycle(
ProvidesMatchersAnnotatedElementMirror target, boolean hasSuper) {
String genericNoParent = target.getSimpleNameOfGeneratedInterfaceMatcherWithGenericNoParent();
String simpleNameGenericNoParent = target.getSimpleNameOfGeneratedImplementationMatcherWithGenericNoParent();
String simpleNameGenericWithParent = target
Expand All @@ -43,23 +44,50 @@ private static DSLMethod generateWithSameValueWithParentMatcherIgnore(ProvidesMa
.orElse("org.hamcrest.Matchers.anything()"))
: "";
String javadoc = target.generateDefaultJavaDocWithoutDSLStarter(Optional.of(
"other the other object to be used as a reference.\n ignoredFields fields name that must be ignored."),
"other the other object to be used as a reference.\nprevious the previous object of the call stack of matcher\nignoredFields fields name that must be ignored."),
"the DSL matcher", false);
List<String> lines = new ArrayList<>();
lines.add("java.util.Set<java.lang.Object> nPrevious = new java.util.HashSet(previous);");
lines.add("nPrevious.add(other);");
lines.add("java.util.Set<String> ignored = new java.util.HashSet<>(java.util.Arrays.asList(ignoredFields));");
lines.add(genericNoParent + " m=new " + simpleNameGenericNoParent + "(" + argumentForParentBuilder + ");");
lines.add("if (previous.stream().anyMatch(p->p==other)) {");
lines.add(
" return m.andWith(org.hamcrest.Matchers.describedAs(\"Same instance control only. A cycle has been detected.\",org.hamcrest.Matchers.sameInstance(other)));");
lines.add("}");
target.getFields().stream().flatMap(ProvidesMatchersWithSameValueHelper::copyField).forEach(lines::add);
lines.add("return m;");
return of(format("%1$s %2$s.%3$s %4$s", fullGeneric, fullyQualified, genericNoParent, withSameValueMethodName))
.withArguments(
new String[][] { { simpleNameGenericWithParent, "other" }, { "String...", "ignoredFields" } })
.withArguments(new String[][] { { simpleNameGenericWithParent, "other" },
{ "java.util.Set<java.lang.Object>", "previous" }, { "String...", "ignoredFields" } })
.withImplementation(lines).withJavadoc(javadoc);
}

private static Stream<String> copyField(AbstractFieldDescription f) {
String args = f.getTargetAsMatchable().filter(Matchable::supportCycleDetectionV1)
.map(x -> ",nPrevious,localIgnored").orElse(",localIgnored");
return stream(format(
"if(!ignored.contains(\"%1$s\")) {\n String localIgnored[] = ignored.stream().filter(s->s.startsWith(\"%1$s.\")).map(s->s.replaceFirst(\"%1$s\\\\.\",\"\")).toArray(String[]::new);\n%2$s\n}",
f.getFieldName(), addPrefix(" ", f.getFieldCopy("m", "other", ",localIgnored"))).split("\n"));
f.getFieldName(), addPrefix(" ", f.getFieldCopy("m", "other", args))).split("\n"));
}

private static DSLMethod generateWithSameValueWithParentMatcherIgnore(
ProvidesMatchersAnnotatedElementMirror target) {
String genericNoParent = target.getSimpleNameOfGeneratedInterfaceMatcherWithGenericNoParent();
String simpleNameGenericWithParent = target
.getFullyQualifiedNameOfClassAnnotatedWithProvideMatcherWithGeneric();
String fullyQualified = target.getFullyQualifiedNameOfGeneratedClass();
String withSameValueMethodName = target.getMethodNameDSLWithSameValue();
String fullGeneric = target.getFullGeneric();
String javadoc = target.generateDefaultJavaDocWithoutDSLStarter(Optional.of(
"other the other object to be used as a reference.\nignoredFields fields name that must be ignored."),
"the DSL matcher", false);
return of(format("%1$s %2$s.%3$s %4$s", fullGeneric, fullyQualified, genericNoParent, withSameValueMethodName))
.withArguments(
new String[][] { { simpleNameGenericWithParent, "other" }, { "String...", "ignoredFields" } })
.withImplementation(format("return %1$s(other,java.util.Collections.emptySet(),ignoredFields);",
withSameValueMethodName))
.withJavadoc(javadoc);
}

private static DSLMethod generateWithSameValueWithParentMatcherAndNoIgnore(
Expand All @@ -69,7 +97,9 @@ private static DSLMethod generateWithSameValueWithParentMatcherAndNoIgnore(
target.getSimpleNameOfGeneratedInterfaceMatcherWithGenericNoParent(), withSameValueMethodName))
.withOneArgument(target.getFullyQualifiedNameOfClassAnnotatedWithProvideMatcherWithGeneric(),
"other")
.withImplementation(format("return %1$s(other,new String[]{});", withSameValueMethodName))
.withImplementation(
format("return %1$s(other,java.util.Collections.emptySet(),new String[]{});",
withSameValueMethodName))
.withJavadoc(target.generateDefaultJavaDocWithoutDSLStarter(
Optional.of("other the other object to be used as a reference."), "the DSL matcher",
false));
Expand All @@ -92,14 +122,23 @@ private static void logWeak(ProvidesMatchersAnnotatedElementMirror target) {
public static Collection<Supplier<DSLMethod>> generateParentValueDSLStarter(
ProvidesMatchersAnnotatedElementMirror target) {
return asList(() -> target.getParentMirror()
.map(parentMirror -> generateWithSameValueWithParentMatcherIgnore(target, true)).orElseGet(() -> {
.map(parentMirror -> generateWithSameValueWithParentMatcherIgnoreAndCycle(target, true))
.orElseGet(() -> {
if (isWeakAllowed(target)) {
logWeak(target);
return generateWithSameValueWithParentMatcherIgnore(target, true);
return generateWithSameValueWithParentMatcherIgnoreAndCycle(target, true);
} else {
return null;
}
}),
}), () -> target.getParentMirror()
.map(parentMirror -> generateWithSameValueWithParentMatcherIgnore(target)).orElseGet(() -> {
if (isWeakAllowed(target)) {
logWeak(target);
return generateWithSameValueWithParentMatcherIgnore(target);
} else {
return null;
}
}),
() -> target.getParentMirror()
.map(parentMirror -> generateWithSameValueWithParentMatcherAndNoIgnore(target))
.orElseGet(() -> {
Expand All @@ -114,7 +153,8 @@ public static Collection<Supplier<DSLMethod>> generateParentValueDSLStarter(

public static Collection<Supplier<DSLMethod>> generateNoParentValueDSLStarter(
ProvidesMatchersAnnotatedElementMirror target) {
return asList(() -> generateWithSameValueWithParentMatcherIgnore(target, false),
return asList(() -> generateWithSameValueWithParentMatcherIgnoreAndCycle(target, false),
() -> generateWithSameValueWithParentMatcherIgnore(target),
() -> generateWithSameValueWithParentMatcherAndNoIgnore(target));
}

Expand Down
Expand Up @@ -105,9 +105,9 @@ public void testGenerateDSLStarter() {
ProvidesMatchersAnnotatedElementMirror underTest = new ProvidesMatchersAnnotatedElementMirror(typeElement,
roundMirror);
Collection<DSLMethod> results = underTest.generateDSLStarter();
assertThat(results).is(iterableWithSize(4));
assertThat(results).is(iterableWithSize(5));
assertThat(results.stream().map(x -> x.asStaticImplementation()).collect(Collectors.joining("\n"))).is(
"/**\n * Start a DSL matcher for the {@link fqn.Sn Sn}.\n * <p>\n * The returned builder (which is also a Matcher), at this point accepts any object that is a {@link fqn.Sn Sn}.\n * \n * \n * @return the DSL matcher\n */\npublic static fqn.SnMatchers.SnMatcher <Void> snWith() {\n return new SnMatcherImpl<Void>();\n}\n\n/**\n * Start a DSL matcher for the {@link fqn.Sn Sn}.\n * <p>\n * The returned builder (which is also a Matcher), at this point accepts any object that is a {@link fqn.Sn Sn}.\n * @param parentBuilder the parentBuilder.\n * \n * \n * @param <_PARENT> used to reference, if necessary, a parent for this builder. By default Void is used an indicate no parent builder.\n * @return the DSL matcher\n */\npublic static <_PARENT> fqn.SnMatchers.SnMatcher <_PARENT> snWithParent(_PARENT parentBuilder) {\n return new SnMatcherImpl<_PARENT>(parentBuilder);\n}\n\n/**\n * Start a DSL matcher for the {@link fqn.Sn Sn}.\n * @param other the other object to be used as a reference.\n * @param ignoredFields fields name that must be ignored.\n * \n * \n * @return the DSL matcher\n */\npublic static fqn.SnMatchers.SnMatcher <Void> snWithSameValue(fqn.Sn other,String... ignoredFields) {\n java.util.Set<String> ignored = new java.util.HashSet<>(java.util.Arrays.asList(ignoredFields));\n SnMatcher <Void> m=new SnMatcherImpl<Void>();\n return m;\n}\n\n/**\n * Start a DSL matcher for the {@link fqn.Sn Sn}.\n * @param other the other object to be used as a reference.\n * \n * \n * @return the DSL matcher\n */\npublic static fqn.SnMatchers.SnMatcher <Void> snWithSameValue(fqn.Sn other) {\n return snWithSameValue(other,new String[]{});\n}\n");
"/**\n * Start a DSL matcher for the {@link fqn.Sn Sn}.\n * <p>\n * The returned builder (which is also a Matcher), at this point accepts any object that is a {@link fqn.Sn Sn}.\n * \n * \n * @return the DSL matcher\n */\npublic static fqn.SnMatchers.SnMatcher <Void> snWith() {\n return new SnMatcherImpl<Void>();\n}\n\n/**\n * Start a DSL matcher for the {@link fqn.Sn Sn}.\n * <p>\n * The returned builder (which is also a Matcher), at this point accepts any object that is a {@link fqn.Sn Sn}.\n * @param parentBuilder the parentBuilder.\n * \n * \n * @param <_PARENT> used to reference, if necessary, a parent for this builder. By default Void is used an indicate no parent builder.\n * @return the DSL matcher\n */\npublic static <_PARENT> fqn.SnMatchers.SnMatcher <_PARENT> snWithParent(_PARENT parentBuilder) {\n return new SnMatcherImpl<_PARENT>(parentBuilder);\n}\n\n/**\n * Start a DSL matcher for the {@link fqn.Sn Sn}.\n * @param other the other object to be used as a reference.\n * @param previous the previous object of the call stack of matcher\n * @param ignoredFields fields name that must be ignored.\n * \n * \n * @return the DSL matcher\n */\npublic static fqn.SnMatchers.SnMatcher <Void> snWithSameValue(fqn.Sn other,java.util.Set<java.lang.Object> previous,String... ignoredFields) {\n java.util.Set<java.lang.Object> nPrevious = new java.util.HashSet(previous);\n nPrevious.add(other);\n java.util.Set<String> ignored = new java.util.HashSet<>(java.util.Arrays.asList(ignoredFields));\n SnMatcher <Void> m=new SnMatcherImpl<Void>();\n if (previous.stream().anyMatch(p->p==other)) {\n return m.andWith(org.hamcrest.Matchers.describedAs(\"Same instance control only. A cycle has been detected.\",org.hamcrest.Matchers.sameInstance(other)));\n }\n return m;\n}\n\n/**\n * Start a DSL matcher for the {@link fqn.Sn Sn}.\n * @param other the other object to be used as a reference.\n * @param ignoredFields fields name that must be ignored.\n * \n * \n * @return the DSL matcher\n */\npublic static fqn.SnMatchers.SnMatcher <Void> snWithSameValue(fqn.Sn other,String... ignoredFields) {\n return snWithSameValue(other,java.util.Collections.emptySet(),ignoredFields);\n}\n\n/**\n * Start a DSL matcher for the {@link fqn.Sn Sn}.\n * @param other the other object to be used as a reference.\n * \n * \n * @return the DSL matcher\n */\npublic static fqn.SnMatchers.SnMatcher <Void> snWithSameValue(fqn.Sn other) {\n return snWithSameValue(other,java.util.Collections.emptySet(),new String[]{});\n}\n");
}

}

0 comments on commit 07423ee

Please sign in to comment.