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