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