001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.bct.assertions;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.function.*;
022
023import org.apache.juneau.commons.utils.*;
024
025/**
026 * Wrapper around a {@link Predicate} that allows for an error message for when the predicate fails.
027 *
028 * <p>
029 * Typically used wherever predicates are allowed for testing of {@link Assertion} objects such as...
030 * <ul>
031 *    <li>{@link FluentObjectAssertion#is(Predicate)}
032 *    <li>{@link FluentArrayAssertion#is(Predicate...)}
033 *    <li>{@link FluentPrimitiveArrayAssertion#is(Predicate...)}
034 *    <li>{@link FluentListAssertion#isEach(Predicate...)}
035 * </ul>
036 *
037 * <p>
038 * See {@link AssertionPredicates} for a set of predefined predicates for common use cases.
039 *
040 * <h5 class='section'>Example:</h5>
041 * <p class='bjava'>
042 *    <jc>// Asserts that a bean passes a custom test.</jc>
043 *    <jc>// AssertionError with specified message is thrown otherwise.</jc>
044 *    Predicate&lt;MyBean&gt; <jv>predicate</jv> = <jk>new</jk> AssertionPredicate&lt;MyBean&gt;(
045 *       <jv>x</jv> -&gt; <jv>x</jv>.getFoo().equals(<js>"bar"</js>),
046 *       <js>"Foo did not equal bar.  Bean was='%s'"</js>,
047 *       <jsf>VALUE</jsf>
048 *    );
049 *    <jsm>assertObject</jsm>(<jv>myBean</jv>).is(<jv>predicate</jv>);
050 * </p>
051 *
052 * <h5 class='section'>See Also:</h5>
053 * <ul>
054 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauEcosystemOverview">Juneau Ecosystem</a>
055 * </ul>
056 *
057 * @param <T> the type of input being tested.
058 */
059@SuppressWarnings({
060   "java:S115", // Constants use UPPER_snakeCase convention (e.g., MSG_predicateTestFailed, MSG_valueDidNotPassTest)
061   "java:S3740" // Raw Predicate/Function types used for dynamic assertion dispatch where T cannot be bounded further
062})
063public class AssertionPredicate<T> implements Predicate<T> {
064
065   private static final String CONST_Messages = "Messages";
066
067   /**
068    * Encapsulates multiple predicates into a single AND operation.
069    *
070    * <p>
071    * Similar to <c><jsm>stream</jsm>(<jv>predicates</jv>).reduce(<jv>x</jv>-&gt;<jk>true</jk>, Predicate::and)</c> but
072    * provides for {@link #getFailureMessage()} to return a useful message.
073    *
074    * @param <T> the type of input being tested.
075    */
076   public static class And<T> extends AssertionPredicate<T> {
077
078      private static final Messages MESSAGES = Messages.of(AssertionPredicate.class, CONST_Messages);
079
080      private static final String MSG_predicateTestFailed = MESSAGES.getString("predicateTestFailed");
081      private final Predicate<T>[] inner;
082
083      /**
084       * Constructor.
085       *
086       * @param inner The inner predicates to run.
087       */
088      @SafeVarargs
089      public And(Predicate<T>...inner) {
090         this.inner = inner;
091      }
092
093      @Override /* Overridden from Predicate */
094      public boolean test(T t) {
095         failedMessage.remove();
096         for (var i = 0; i < inner.length; i++) {
097            var p = inner[i];
098            if (nn(p)) {
099               var b = p.test(t);
100               if (! b) {
101                  var m = f(MSG_predicateTestFailed, i + 1);
102                  if (p instanceof AssertionPredicate<?> p2)
103                     m += "\n\t" + p2.getFailureMessage();
104                  failedMessage.set(m);
105                  return false;
106               }
107            }
108         }
109         return true;
110      }
111   }
112
113   /**
114    * Negates an assertion.
115    *
116    * <p>
117    * Similar to <c><jv>predicate</jv>.negate()</c> but provides for {@link #getFailureMessage()} to return a useful message.
118    *
119    * @param <T> the type of input being tested.
120    */
121   public static class Not<T> extends AssertionPredicate<T> {
122
123      private static final Messages MESSAGES = Messages.of(AssertionPredicate.class, CONST_Messages);
124      private static final String MSG_predicateTestsUnexpectedlyPassed = MESSAGES.getString("predicateTestsUnexpectedlyPassed");
125
126      private final Predicate<T> inner;
127
128      /**
129       * Constructor.
130       *
131       * @param inner The inner predicates to run.
132       */
133      public Not(Predicate<T> inner) {
134         this.inner = inner;
135      }
136
137      @Override /* Overridden from Predicate */
138      public boolean test(T t) {
139         failedMessage.remove();
140         var p = inner;
141         if (nn(p)) {
142            var b = p.test(t);
143            if (b) {
144               failedMessage.set(f(MSG_predicateTestsUnexpectedlyPassed));
145               return false;
146            }
147         }
148         return true;
149      }
150   }
151
152   /**
153    * Encapsulates multiple predicates into a single OR operation.
154    *
155    * <p>
156    * Similar to <c><jsm>stream</jsm>(<jv>predicates</jv>).reduce(<jv>x</jv>-&gt;<jk>true</jk>, Predicate::or)</c> but
157    * provides for {@link #getFailureMessage()} to return a useful message.
158    *
159    * @param <T> the type of input being tested.
160    */
161   public static class Or<T> extends AssertionPredicate<T> {
162
163      private static final Messages MESSAGES = Messages.of(AssertionPredicate.class, CONST_Messages);
164      private static final String MSG_noPredicateTestsPassed = MESSAGES.getString("noPredicateTestsPassed");
165
166      private final Predicate<T>[] inner;
167
168      /**
169       * Constructor.
170       *
171       * @param inner The inner predicates to run.
172       */
173      @SafeVarargs
174      public Or(Predicate<T>...inner) {
175         this.inner = inner;
176      }
177
178      @Override /* Overridden from Predicate */
179      public boolean test(T t) {
180         failedMessage.remove();
181         for (var p : inner)
182            if (nn(p) && p.test(t))
183               return true;
184         var m = f(MSG_noPredicateTestsPassed);
185         failedMessage.set(m);
186         return false;
187      }
188   }
189
190   /**
191    * Argument placeholder for tested value.
192    */
193   public static final Function<Object,String> VALUE = StringUtils::readable;
194   private static final Messages MESSAGES = Messages.of(AssertionPredicate.class, CONST_Messages);
195   private static final String MSG_valueDidNotPassTest = MESSAGES.getString("valueDidNotPassTest");
196   private static final String MSG_valueDidNotPassTestWithValue = MESSAGES.getString("valueDidNotPassTestWithValue");
197   private final Predicate<T> inner;
198
199   private final String message;
200
201   private final Object[] args;
202
203   final ThreadLocal<String> failedMessage = new ThreadLocal<>();
204
205   /**
206    * Constructor.
207    *
208    * @param inner The predicate test.
209    * @param message
210    *    The error message if predicate fails.
211    *    <br>Supports printf-style arguments.
212    * @param args
213    *    Optional message arguments.
214    *    <br>Can contain {@link #VALUE} to specify the value itself as an argument.
215    *    <br>Can contain {@link Function functions} to apply to the tested value.
216    */
217   public AssertionPredicate(Predicate<T> inner, String message, Object...args) {
218      this.inner = inner;
219      if (nn(message)) {
220         this.message = message;
221         this.args = args;
222      } else if (inner instanceof AssertionPredicate) {
223         this.message = MSG_valueDidNotPassTest;
224         this.args = a();
225      } else {
226         this.message = MSG_valueDidNotPassTestWithValue;
227         this.args = a(VALUE);
228      }
229   }
230
231   AssertionPredicate() {
232      this.inner = null;
233      this.message = null;
234      this.args = null;
235   }
236
237   @Override /* Overridden from Predicate */
238   @SuppressWarnings({
239      "unchecked" // Type erasure requires cast for predicate test
240   })
241   public boolean test(T t) {
242      failedMessage.remove();
243      var b = inner.test(t);
244      if (! b) {
245         var m = message;
246         var oargs = new Object[this.args.length];
247         for (var i = 0; i < oargs.length; i++) {
248            var a = this.args[i];
249            if (a instanceof Function a2)
250               oargs[i] = r(a2.apply(t));
251            else
252               oargs[i] = r(a);
253         }
254         m = f(m, oargs);
255         if (inner instanceof AssertionPredicate<?> inner2)
256            m += "\n\t" + inner2.getFailureMessage();
257         failedMessage.set(m);
258      }
259      return inner.test(t);
260   }
261
262   /**
263    * Returns the error message from the last call to this assertion.
264    *
265    * @return The error message, or <jk>null</jk> if there was no failure.
266    */
267   protected String getFailureMessage() { return failedMessage.get(); }
268}