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.assertions;
018
019import static org.apache.juneau.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.commons.utils.Utils.*;
021
022import java.text.*;
023import java.util.function.*;
024
025import org.apache.juneau.commons.utils.*;
026import org.apache.juneau.cp.*;
027
028/**
029 * Wrapper around a {@link Predicate} that allows for an error message for when the predicate fails.
030 *
031 * <p>
032 * Typically used wherever predicates are allowed for testing of {@link Assertion} objects such as...
033 * <ul>
034 *    <li>{@link FluentObjectAssertion#is(Predicate)}
035 *    <li>{@link FluentArrayAssertion#is(Predicate...)}
036 *    <li>{@link FluentPrimitiveArrayAssertion#is(Predicate...)}
037 *    <li>{@link FluentListAssertion#isEach(Predicate...)}
038 * </ul>
039 *
040 * <p>
041 * See {@link AssertionPredicates} for a set of predefined predicates for common use cases.
042 *
043 * <h5 class='section'>Example:</h5>
044 * <p class='bjava'>
045 *    <jc>// Asserts that a bean passes a custom test.</jc>
046 *    <jc>// AssertionError with specified message is thrown otherwise.</jc>
047 *    Predicate&lt;MyBean&gt; <jv>predicate</jv> = <jk>new</jk> AssertionPredicate&lt;MyBean&gt;(
048 *       <jv>x</jv> -&gt; <jv>x</jv>.getFoo().equals(<js>"bar"</js>),
049 *       <js>"Foo did not equal bar.  Bean was=''{0}''"</js>,
050 *       <jsf>VALUE</jsf>
051 *    );
052 *    <jsm>assertObject</jsm>(<jv>myBean</jv>).is(<jv>predicate</jv>);
053 * </p>
054 *
055 * <h5 class='section'>See Also:</h5>
056 * <ul>
057 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauEcosystemOverview">Juneau Ecosystem Overview</a>
058 * </ul>
059 *
060 * @param <T> the type of input being tested.
061 */
062public class AssertionPredicate<T> implements Predicate<T> {
063
064   /**
065    * Encapsulates multiple predicates into a single AND operation.
066    *
067    * <p>
068    * Similar to <c><jsm>stream</jsm>(<jv>predicates</jv>).reduce(<jv>x</jv>-&gt;<jk>true</jk>, Predicate::and)</c> but
069    * provides for {@link #getFailureMessage()} to return a useful message.
070    *
071    * @param <T> the type of input being tested.
072    */
073   public static class And<T> extends AssertionPredicate<T> {
074
075      private static final Messages MESSAGES = Messages.of(AssertionPredicate.class, "Messages");
076
077      private static final String MSG_predicateTestFailed = MESSAGES.getString("predicateTestFailed");
078      private final Predicate<T>[] inner;
079
080      /**
081       * Constructor.
082       *
083       * @param inner The inner predicates to run.
084       */
085      @SafeVarargs
086      public And(Predicate<T>...inner) {
087         this.inner = inner;
088      }
089
090      @Override /* Overridden from Predicate */
091      public boolean test(T t) {
092         failedMessage.remove();
093         for (var i = 0; i < inner.length; i++) {
094            var p = inner[i];
095            if (nn(p)) {
096               var b = p.test(t);
097               if (! b) {
098                  var m = f(MSG_predicateTestFailed, i + 1);
099                  if (p instanceof AssertionPredicate p2) // NOSONAR - Intentional.
100                     m += "\n\t" + p2.getFailureMessage();
101                  failedMessage.set(m);
102                  return false;
103               }
104            }
105         }
106         return true;
107      }
108   }
109
110   /**
111    * Negates an assertion.
112    *
113    * <p>
114    * Similar to <c><jv>predicate</jv>.negate()</c> but provides for {@link #getFailureMessage()} to return a useful message.
115    *
116    * @param <T> the type of input being tested.
117    */
118   public static class Not<T> extends AssertionPredicate<T> {
119
120      private static final Messages MESSAGES = Messages.of(AssertionPredicate.class, "Messages");
121      private static final String MSG_predicateTestsUnexpectedlyPassed = MESSAGES.getString("predicateTestsUnexpectedlyPassed");
122
123      private final Predicate<T> inner;
124
125      /**
126       * Constructor.
127       *
128       * @param inner The inner predicates to run.
129       */
130      public Not(Predicate<T> inner) {
131         this.inner = inner;
132      }
133
134      @Override /* Overridden from Predicate */
135      public boolean test(T t) {
136         failedMessage.remove();
137         var p = inner;
138         if (nn(p)) {
139            var b = p.test(t);
140            if (b) {
141               failedMessage.set(f(MSG_predicateTestsUnexpectedlyPassed));
142               return false;
143            }
144         }
145         return true;
146      }
147   }
148
149   /**
150    * Encapsulates multiple predicates into a single OR operation.
151    *
152    * <p>
153    * Similar to <c><jsm>stream</jsm>(<jv>predicates</jv>).reduce(<jv>x</jv>-&gt;<jk>true</jk>, Predicate::or)</c> but
154    * provides for {@link #getFailureMessage()} to return a useful message.
155    *
156    * @param <T> the type of input being tested.
157    */
158   public static class Or<T> extends AssertionPredicate<T> {
159
160      private static final Messages MESSAGES = Messages.of(AssertionPredicate.class, "Messages");
161      private static final String MSG_noPredicateTestsPassed = MESSAGES.getString("noPredicateTestsPassed");
162
163      private final Predicate<T>[] inner;
164
165      /**
166       * Constructor.
167       *
168       * @param inner The inner predicates to run.
169       */
170      @SafeVarargs
171      public Or(Predicate<T>...inner) {
172         this.inner = inner;
173      }
174
175      @Override /* Overridden from Predicate */
176      public boolean test(T t) {
177         failedMessage.remove();
178         for (var p : inner)
179            if (nn(p) && p.test(t))
180               return true;
181         var m = f(MSG_noPredicateTestsPassed);
182         failedMessage.set(m);
183         return false;
184      }
185   }
186
187   /**
188    * Argument placeholder for tested value.
189    */
190   public static final Function<Object,String> VALUE = StringUtils::readable;
191   private static final Messages MESSAGES = Messages.of(AssertionPredicate.class, "Messages");
192   // @formatter:off
193   private static final String
194      MSG_valueDidNotPassTest = MESSAGES.getString("valueDidNotPassTest"),
195      MSG_valueDidNotPassTestWithValue = MESSAGES.getString("valueDidNotPassTestWithValue");
196   // @formatter:on
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 {@link MessageFormat}-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("unchecked")
239   public boolean test(T t) {
240      failedMessage.remove();
241      var b = inner.test(t);
242      if (! b) {
243         var m = message;
244         var oargs = new Object[this.args.length];
245         for (var i = 0; i < oargs.length; i++) {
246            var a = this.args[i];
247            if (a instanceof Function a2) // NOSONAR - Intentional.
248               oargs[i] = r(a2.apply(t));
249            else
250               oargs[i] = r(a);
251         }
252         m = f(m, oargs);
253         if (inner instanceof AssertionPredicate inner2) // NOSONAR - Intentional.
254            m += "\n\t" + inner2.getFailureMessage();
255         failedMessage.set(m);
256      }
257      return inner.test(t);
258   }
259
260   /**
261    * Returns the error message from the last call to this assertion.
262    *
263    * @return The error message, or <jk>null</jk> if there was no failure.
264    */
265   protected String getFailureMessage() { return failedMessage.get(); }
266}