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