Repository: assertj/assertj

Problem statement:
Mapped-Condition
**Mapped**: A Condition that describes the way of Mapping to an nested Condition.


Hints:
Is there anything i can to to get this pr into the next release?
I'll try have a look, it is not so clear what problem you are trying to solve, an example would be welcome
Thank you,

the main goal is to have a Container-Condition  that does a mapping and then usees nested Conditions to test the mapped actual value.

Example:
https://github.com/osgi/osgi-test/blob/5598f37b2aac45f6e6eb744310587198d39f445b/org.osgi.test.assertj.framework/src/main/java/org/osgi/test/assertj/conditions/Conditions.java#L937-L966

There are several more in the file.
Thanks, I'll put it back to the next release then

Patch:
diff --git a/src/main/java/org/assertj/core/condition/MappedCondition.java b/src/main/java/org/assertj/core/condition/MappedCondition.java
new file mode 100644
--- /dev/null
+++ b/src/main/java/org/assertj/core/condition/MappedCondition.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * Copyright 2012-2021 the original author or authors.
+ */
+package org.assertj.core.condition;
+
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+import java.util.function.Function;
+
+import org.assertj.core.annotations.Beta;
+import org.assertj.core.api.Condition;
+
+/**
+ * Container-<code>{@link Condition}</code> that does a mapping and then uses nested
+ * <code>{@link Condition}</code> to test the mapped actual value.
+ *
+ * <pre><code class='java'> Condition&lt;String&gt; hasLineSeparator = new Condition&lt;&gt;(t -&gt; t.contains(System.lineSeparator()), "has lineSeparator");
+ *
+ * Condition&lt;Optional&lt;String&gt;&gt; optionalHasLineSeparator = MappedCondition.mappedCondition(Optional::get, hasLineSeparator, "optional value has lineSeparator");
+ *
+ * // returns true
+ * optionalHasLineSeparator.matches(Optional.of("a" + System.lineSeparator()));
+ *
+ * // returns false
+ * optionalHasLineSeparator.matches(Optional.of("a"));</code></pre>
+ *
+ * @param <FROM> the type of object this condition accepts.
+ * @param <TO> the type of object the nested condition accepts.
+ *
+ * @author Stefan Bischof
+ */
+@Beta
+public class MappedCondition<FROM, TO> extends Condition<FROM> {
+
+  private Condition<TO> condition;
+  private Function<FROM, TO> mapping;
+  private String mappingDescription;
+
+  /**
+   * Creates a new <code>{@link MappedCondition}</code>.
+   * <p>
+   * Note that the mappingDescription argument follows {@link String#format(String, Object...)} syntax.
+   *
+   * @param <FROM> the type of object the given condition accept.
+   * @param <TO> the type of object the nested condition accept.
+   * @param mapping the Function that maps the value to test to the a value for the nested condition.
+   * @param condition the nested condition to evaluate.
+   * @param mappingDescription describes the mapping, follows {@link String#format(String, Object...)} syntax.
+   * @param args for describing the mapping as in {@link String#format(String, Object...)} syntax.
+   * @return the created {@code MappedCondition}.
+   * @throws NullPointerException if the given condition is {@code null}.
+   * @throws NullPointerException if the given mapping is {@code null}.
+   */
+  public static <FROM, TO> MappedCondition<FROM, TO> mappedCondition(Function<FROM, TO> mapping, Condition<TO> condition,
+                                                                     String mappingDescription, Object... args) {
+    requireNonNull(mappingDescription, "The given mappingDescription should not be null");
+    return new MappedCondition<>(mapping, condition, format(mappingDescription, args));
+  }
+
+  /**
+   * Creates a new <code>{@link MappedCondition}</code>
+   *
+   * @param <FROM> the type of object the given condition accept.
+   * @param <TO> the type of object the nested condition accept.
+   * @param mapping the Function that maps the value to test to the a value for the nested condition.
+   * @param condition the nested condition to evaluate.
+   * @return the created {@code MappedCondition}.
+   * @throws NullPointerException if the given condition is {@code null}.
+   * @throws NullPointerException if the given mapping is {@code null}.
+   */
+  public static <FROM, TO> MappedCondition<FROM, TO> mappedCondition(Function<FROM, TO> mapping, Condition<TO> condition) {
+    return mappedCondition(mapping, condition, "");
+  }
+
+  private MappedCondition(Function<FROM, TO> mapping, Condition<TO> condition, String mappingDescription) {
+    requireNonNull(condition, "The given condition should not be null");
+    requireNonNull(mapping, "The given mapping function should not be null");
+    this.mapping = mapping;
+    this.mappingDescription = mappingDescription;
+    this.condition = condition;
+  }
+
+  /**
+   * Maps the value with the given function and verifies that it satisfies the nested <code>{@link Condition}</code>.
+   *
+   * @param value the value to map
+   * @return {@code true} if the given mapped value satisfies the nested condition; {@code false} otherwise.
+   */
+  @Override
+  public boolean matches(FROM value) {
+    TO mappedObject = mapping.apply(value);
+    String desc = buildMappingDescription(value, mappedObject);
+    describedAs(desc);
+    return condition.matches(mappedObject);
+  }
+
+  /**
+   * Build the mapped condition description when applied with the FROM and TO values.
+   *
+   * @param from the value to map
+   * @param to the mapped value
+   * @return the mapped condition description .
+   */
+  protected String buildMappingDescription(FROM from, TO to) {
+    StringBuilder sb = new StringBuilder();
+    sb.append("mapped");
+    if (!mappingDescription.isEmpty()) sb.append(format("%n   using: %s", mappingDescription));
+    sb.append(format("%n   from: <%s> %s%n", from.getClass().getSimpleName(), from));
+    sb.append(format("   to:   <%s> %s%n", to.getClass().getSimpleName(), from, to));
+    sb.append("   then checked:");
+    sb.append(format("%n      %-10s", condition));
+    return sb.toString();
+  }
+
+}


Test patch:
diff --git a/src/test/java/org/assertj/core/condition/MappedConditionTest.java b/src/test/java/org/assertj/core/condition/MappedConditionTest.java
new file mode 100644
--- /dev/null
+++ b/src/test/java/org/assertj/core/condition/MappedConditionTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * Copyright 2012-2021 the original author or authors.
+ */
+package org.assertj.core.condition;
+
+import static java.lang.String.format;
+import static java.lang.System.lineSeparator;
+import static org.assertj.core.api.BDDAssertions.then;
+import static org.assertj.core.api.BDDAssertions.thenNullPointerException;
+import static org.assertj.core.condition.MappedCondition.mappedCondition;
+
+import java.util.Optional;
+
+import org.assertj.core.api.Condition;
+import org.junit.jupiter.api.Test;
+
+class MappedConditionTest {
+
+  private static final String INNER_CONDITION_DESCRIPTION = "isString and BAR";
+
+  private static final String BAR = "bar";
+
+  private static final String FOO = "foo";
+
+  private final static Condition<String> isBarString = new Condition<>(s -> BAR.equals(s), INNER_CONDITION_DESCRIPTION);
+
+  private final static String BAR_CONDITION_DESCRIPTION = format("mapped%n" +
+                                                                 "   using: ::toString%n" +
+                                                                 "   from: <StringBuilder> " + BAR + "%n" +
+                                                                 "   to:   <String> " + BAR + "%n" +
+                                                                 "   then checked:%n" +
+                                                                 "      " + INNER_CONDITION_DESCRIPTION);
+
+  private final static String BAR_CONDITION_DESCRIPTION_PLAIN = format("mapped%n" +
+                                                                       "   from: <StringBuilder> " + BAR + "%n" +
+                                                                       "   to:   <String> " + BAR + "%n" +
+                                                                       "   then checked:%n" +
+                                                                       "      " + INNER_CONDITION_DESCRIPTION);
+
+  private final static String FOO_CONDITION_DESCRIPTION = format("mapped%n" +
+                                                                 "   using: ::toString%n" +
+                                                                 "   from: <StringBuilder> " + FOO + "%n" +
+                                                                 "   to:   <String> " + FOO + "%n" +
+                                                                 "   then checked:%n" +
+                                                                 "      " + INNER_CONDITION_DESCRIPTION);
+
+  @Test
+  void mappedCondition_withDescription_works() {
+    // WHEN
+    Condition<StringBuilder> mappedCondition = mappedCondition(StringBuilder::toString, isBarString, "%stoString", "::");
+    // THEN
+    then(mappedCondition.matches(new StringBuilder(BAR))).isTrue();
+    then(mappedCondition).hasToString(BAR_CONDITION_DESCRIPTION);
+    then(mappedCondition.matches(new StringBuilder(FOO))).isFalse();
+    then(mappedCondition).hasToString(FOO_CONDITION_DESCRIPTION);
+  }
+
+  @Test
+  void mappedCondition_withoutDescription_works() {
+    // WHEN
+    Condition<StringBuilder> mappedCondition = mappedCondition(StringBuilder::toString, isBarString);
+    // THEN
+    then(mappedCondition.matches(new StringBuilder(BAR))).isTrue();
+    then(mappedCondition).hasToString(BAR_CONDITION_DESCRIPTION_PLAIN);
+  }
+
+  @Test
+  void mappedCondition_with_description_and_null_condition_should_throw_NPE() {
+    // GIVEN
+    Condition<String> nullCondition = null;
+    // WHEN/THEN
+    thenNullPointerException().isThrownBy(() -> mappedCondition(StringBuilder::toString, nullCondition, "::toString"))
+                              .withMessage("The given condition should not be null");
+  }
+
+  @Test
+  void mappedCondition_with_description_and_null_mapping_function_should_throw_NPE() {
+    thenNullPointerException().isThrownBy(() -> mappedCondition(null, isBarString, "::toString"))
+                              .withMessage("The given mapping function should not be null");
+  }
+
+  @Test
+  void mappedCondition_without_description_and_null_condition_should_throw_NPE() {
+    // GIVEN
+    Condition<String> nullCondition = null;
+    // WHEN/THEN
+    thenNullPointerException().isThrownBy(() -> mappedCondition(StringBuilder::toString, nullCondition))
+                              .withMessage("The given condition should not be null");
+  }
+
+  @Test
+  void mappedCondition_without_description_and_null_mapping_function_should_throw_NPE() {
+    thenNullPointerException().isThrownBy(() -> mappedCondition(null, isBarString))
+                              .withMessage("The given mapping function should not be null");
+  }
+
+  @Test
+  void mappedCondition_with_null_description_and_should_throw_NPE() {
+    // GIVEN
+    String nullDescription = null;
+    // WHEN/THEN
+    thenNullPointerException().isThrownBy(() -> mappedCondition(StringBuilder::toString, isBarString, nullDescription))
+                              .withMessage("The given mappingDescription should not be null");
+  }
+
+  @Test
+  void example() {
+    // GIVEN
+    Condition<String> hasLineSeparator = new Condition<>(text -> text.contains(lineSeparator()), "has lineSeparator");
+    Optional<String> optionalString = Optional.of("a" + lineSeparator());
+    // WHEN
+    Condition<Optional<String>> mappedCondition = mappedCondition(Optional<String>::get, hasLineSeparator);
+    boolean matches = mappedCondition.matches(optionalString);
+    // THEN
+    then(matches).isTrue();
+  }
+}