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<MyBean> <jv>predicate</jv> = <jk>new</jk> AssertionPredicate<MyBean>( 048 * <jv>x</jv> -> <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>-><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>-><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}