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