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 java.util.Collections.*; 016import static org.apache.juneau.common.internal.ArgUtils.*; 017import static org.apache.juneau.common.internal.ThrowableUtils.*; 018import static org.apache.juneau.internal.CollectionUtils.list; 019 020import java.io.*; 021import java.util.*; 022import java.util.function.*; 023 024import org.apache.juneau.cp.*; 025import org.apache.juneau.internal.*; 026import org.apache.juneau.serializer.*; 027 028/** 029 * Used for fluent assertion calls against throwables. 030 * 031 * <h5 class='section'>Test Methods:</h5> 032 * <p> 033 * <ul class='javatree'> 034 * <li class='jc'>{@link FluentObjectAssertion} 035 * <ul class='javatreec'> 036 * <li class='jm'>{@link FluentObjectAssertion#isExists() isExists()} 037 * <li class='jm'>{@link FluentObjectAssertion#is(Object) is(Object)} 038 * <li class='jm'>{@link FluentObjectAssertion#is(Predicate) is(Predicate)} 039 * <li class='jm'>{@link FluentObjectAssertion#isNot(Object) isNot(Object)} 040 * <li class='jm'>{@link FluentObjectAssertion#isAny(Object...) isAny(Object...)} 041 * <li class='jm'>{@link FluentObjectAssertion#isNotAny(Object...) isNotAny(Object...)} 042 * <li class='jm'>{@link FluentObjectAssertion#isNull() isNull()} 043 * <li class='jm'>{@link FluentObjectAssertion#isNotNull() isNotNull()} 044 * <li class='jm'>{@link FluentObjectAssertion#isString(String) isString(String)} 045 * <li class='jm'>{@link FluentObjectAssertion#isJson(String) isJson(String)} 046 * <li class='jm'>{@link FluentObjectAssertion#isSame(Object) isSame(Object)} 047 * <li class='jm'>{@link FluentObjectAssertion#isSameJsonAs(Object) isSameJsonAs(Object)} 048 * <li class='jm'>{@link FluentObjectAssertion#isSameSortedJsonAs(Object) isSameSortedJsonAs(Object)} 049 * <li class='jm'>{@link FluentObjectAssertion#isSameSerializedAs(Object, WriterSerializer) isSameSerializedAs(Object, WriterSerializer)} 050 * <li class='jm'>{@link FluentObjectAssertion#isType(Class) isType(Class)} 051 * <li class='jm'>{@link FluentObjectAssertion#isExactType(Class) isExactType(Class)} 052 * </ul> 053 * </ul> 054 * 055 * <h5 class='section'>Transform Methods:</h5> 056 * <p> 057 * <ul class='javatree'> 058 * <li class='jc'>{@link FluentThrowableAssertion} 059 * <ul class='javatreec'> 060 * <li class='jm'>{@link FluentThrowableAssertion#asMessage() asMessage()} 061 * <li class='jm'>{@link FluentThrowableAssertion#asMessages() asMessages()} 062 * <li class='jm'>{@link FluentThrowableAssertion#asLocalizedMessage() asLocalizedMessage()} 063 * <li class='jm'>{@link FluentThrowableAssertion#asLocalizedMessages() asLocalizedMessages()} 064 * <li class='jm'>{@link FluentThrowableAssertion#asStackTrace() asStackTrace()} 065 * <li class='jm'>{@link FluentThrowableAssertion#asCausedBy() asCausedBy()} 066 * <li class='jm'>{@link FluentThrowableAssertion#asCausedBy(Class) asCausedBy(Class)} 067 * <li class='jm'>{@link FluentThrowableAssertion#asFind(Class) asFind(Class)} 068 * </ul> 069 * <li class='jc'>{@link FluentObjectAssertion} 070 * <ul class='javatreec'> 071 * <li class='jm'>{@link FluentObjectAssertion#asString() asString()} 072 * <li class='jm'>{@link FluentObjectAssertion#asString(WriterSerializer) asString(WriterSerializer)} 073 * <li class='jm'>{@link FluentObjectAssertion#asString(Function) asString(Function)} 074 * <li class='jm'>{@link FluentObjectAssertion#asJson() asJson()} 075 * <li class='jm'>{@link FluentObjectAssertion#asJsonSorted() asJsonSorted()} 076 * <li class='jm'>{@link FluentObjectAssertion#asTransformed(Function) asApplied(Function)} 077 * <li class='jm'>{@link FluentObjectAssertion#asAny() asAny()} 078 * </ul> 079 * </ul> 080 * 081 * <h5 class='section'>Configuration Methods:</h5> 082 * <p> 083 * <ul class='javatree'> 084 * <li class='jc'>{@link Assertion} 085 * <ul class='javatreec'> 086 * <li class='jm'>{@link Assertion#setMsg(String, Object...) setMsg(String, Object...)} 087 * <li class='jm'>{@link Assertion#setOut(PrintStream) setOut(PrintStream)} 088 * <li class='jm'>{@link Assertion#setSilent() setSilent()} 089 * <li class='jm'>{@link Assertion#setStdOut() setStdOut()} 090 * <li class='jm'>{@link Assertion#setThrowable(Class) setThrowable(Class)} 091* </ul> 092 * </ul> 093 * 094 * <h5 class='section'>See Also:</h5><ul> 095 * <li class='link'><a class="doclink" href="../../../../index.html#ja.Overview">Overview > juneau-assertions > Overview</a> 096 * </ul> 097 * 098 * @param <T> The throwable type. 099 * @param <R> The return type. 100 */ 101@FluentSetters(returns="FluentThrowableAssertion<T,R>") 102public class FluentThrowableAssertion<T extends Throwable,R> extends FluentObjectAssertion<T,R> { 103 104 //----------------------------------------------------------------------------------------------------------------- 105 // Static 106 //----------------------------------------------------------------------------------------------------------------- 107 108 private static final Messages MESSAGES = Messages.of(FluentThrowableAssertion.class, "Messages"); 109 private static final String 110 MSG_exceptionWasNotExpectedType = MESSAGES.getString("exceptionWasNotExpectedType"), 111 MSG_exceptionWasNotThrown = MESSAGES.getString("exceptionWasNotThrown"), 112 MSG_causedByExceptionNotExpectedType = MESSAGES.getString("causedByExceptionNotExpectedType"); 113 114 //----------------------------------------------------------------------------------------------------------------- 115 // Instance 116 //----------------------------------------------------------------------------------------------------------------- 117 118 /** 119 * Constructor. 120 * 121 * @param value 122 * The object being tested. 123 * <br>Can be <jk>null</jk>. 124 * @param returns 125 * The object to return after a test method is called. 126 * <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be 127 * used on the same assertion. 128 */ 129 public FluentThrowableAssertion(T value, R returns) { 130 this(null, value, returns); 131 } 132 133 /** 134 * Chained constructor. 135 * 136 * <p> 137 * Used when transforming one assertion into another so that the assertion config can be used by the new assertion. 138 * 139 * @param creator 140 * The assertion that created this assertion. 141 * <br>Should be <jk>null</jk> if this is the top-level assertion. 142 * @param value 143 * The object being tested. 144 * <br>Can be <jk>null</jk>. 145 * @param returns 146 * The object to return after a test method is called. 147 * <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be 148 * used on the same assertion. 149 */ 150 public FluentThrowableAssertion(Assertion creator, T value, R returns) { 151 super(creator, value, returns); 152 } 153 154 //----------------------------------------------------------------------------------------------------------------- 155 // Transform methods 156 //----------------------------------------------------------------------------------------------------------------- 157 158 @Override /* FluentObjectAssertion */ 159 public FluentThrowableAssertion<T,R> asTransformed(Function<T,T> function) { 160 return new FluentThrowableAssertion<>(this, function.apply(orElse(null)), returns()); 161 } 162 163 /** 164 * Returns an assertion against the throwable message. 165 * 166 * <h5 class='section'>Example:</h5> 167 * <p class='bjava'> 168 * <jc>// Asserts that the specified method throws an exception 169 * // with 'foobar' somewhere in the messages. </jc> 170 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 171 * .asMessage() 172 * .isPattern(<js>".*foobar.*"</js>); 173 * </p> 174 * 175 * @return An assertion against the throwable message. Never <jk>null</jk>. 176 */ 177 public FluentStringAssertion<R> asMessage() { 178 return new FluentStringAssertion<>(this, map(Throwable::getMessage).orElse(null), returns()); 179 } 180 181 /** 182 * Returns an assertion against the throwable message and all caused-by messages. 183 * 184 * <h5 class='section'>Example:</h5> 185 * <p class='bjava'> 186 * <jc>// Asserts that the specified method throws an exception with 187 * // 'foobar' somewhere in the messages. </jc> 188 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 189 * .asMessages() 190 * .isPattern(<js>".*foobar.*"</js>); 191 * </p> 192 * 193 * @return An assertion against the throwable message. Never <jk>null</jk>. 194 */ 195 public FluentListAssertion<String,R> asMessages() { 196 List<String> l = null; 197 Throwable t = orElse(null); 198 if (t != null) { 199 if (t.getCause() == null) 200 l = singletonList(t.getMessage()); 201 else { 202 l = list(); 203 while (t != null) { 204 l.add(t.getMessage()); 205 t = t.getCause(); 206 } 207 } 208 } 209 return new FluentListAssertion<>(this, l, returns()); 210 } 211 212 /** 213 * Returns an assertion against the throwable localized message. 214 * 215 * <h5 class='section'>Example:</h5> 216 * <p class='bjava'> 217 * <jc>// Asserts that the specified method throws an exception with 218 * // 'foobar' somewhere in the localized messages. </jc> 219 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 220 * .asLocalizedMessage() 221 * .isPattern(<js>".*foobar.*"</js>); 222 * </p> 223 * 224 * @return An assertion against the throwable localized message. Never <jk>null</jk>. 225 */ 226 public FluentStringAssertion<R> asLocalizedMessage() { 227 return new FluentStringAssertion<>(this, map(Throwable::getLocalizedMessage).orElse(null), returns()); 228 } 229 230 /** 231 * Returns an assertion against the throwable message and all caused-by messages. 232 * 233 * <h5 class='section'>Example:</h5> 234 * <p class='bjava'> 235 * <jc>// Asserts that the specified method throws an exception with 236 * // 'foobar' somewhere in the messages. </jc> 237 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 238 * .asLocalizedMessages() 239 * .isPattern(<js>".*foobar.*"</js>); 240 * </p> 241 * 242 * @return An assertion against the throwable message. Never <jk>null</jk>. 243 */ 244 public FluentListAssertion<String,R> asLocalizedMessages() { 245 List<String> l = null; 246 Throwable t = orElse(null); 247 if (t != null) { 248 if (t.getCause() == null) 249 l = singletonList(t.getMessage()); 250 else { 251 l = list(); 252 while (t != null) { 253 l.add(t.getLocalizedMessage()); 254 t = t.getCause(); 255 } 256 } 257 } 258 return new FluentListAssertion<>(this, l, returns()); 259 } 260 261 /** 262 * Returns an assertion against the throwable localized message. 263 * 264 * <h5 class='section'>Example:</h5> 265 * <p class='bjava'> 266 * <jc>// Asserts that the specified method throws an exception with 267 * // 'foobar' somewhere in the stack trace. </jc> 268 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 269 * .asStackTrace() 270 * .isPattern(<js>"foobar"</js>); 271 * </p> 272 * 273 * @return An assertion against the throwable stacktrace. Never <jk>null</jk>. 274 */ 275 public FluentStringListAssertion<R> asStackTrace() { 276 return new FluentStringListAssertion<>(this, valueIsNull() ? null : Arrays.asList(getStackTrace(value())), returns()); 277 } 278 279 /** 280 * Returns an assertion against the caused-by throwable. 281 * 282 * <h5 class='section'>Example:</h5> 283 * <p class='bjava'> 284 * <jc>// Asserts that the specified method throws an exception whose 285 * // caused-by message contains 'foobar'. </jc> 286 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 287 * .asCausedBy() 288 * .asMessage() 289 * .isPattern(<js>"foobar"</js>); 290 * </p> 291 * 292 * @return An assertion against the caused-by. Never <jk>null</jk>. 293 */ 294 public FluentThrowableAssertion<Throwable,R> asCausedBy() { 295 return asCausedBy(Throwable.class); 296 } 297 298 /** 299 * Returns an assertion against the caused-by throwable. 300 * 301 * <h5 class='section'>Example:</h5> 302 * <p class='bjava'> 303 * <jc>// Asserts that the specified method throws an exception whose 304 * // caused-by message contains 'foobar'. </jc> 305 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 306 * .asCausedBy(RuntimeException.<jk>class</jk>) 307 * .asMessage() 308 * .isPattern(<js>"foobar"</js>); 309 * </p> 310 * 311 * @param <X> The throwable type. 312 * @param type The expected exception type. 313 * @return An assertion against the caused-by. Never <jk>null</jk>. 314 */ 315 public <X extends Throwable> FluentThrowableAssertion<X,R> asCausedBy(Class<X> type) { 316 Throwable t = map(Throwable::getCause).orElse(null); 317 if (t == null || type.isInstance(t)) 318 return new FluentThrowableAssertion<>(this, type.cast(t), returns()); 319 throw error(MSG_causedByExceptionNotExpectedType, type, t.getClass()); 320 } 321 322 /** 323 * Returns an assertion against the throwable localized message. 324 * 325 * <h5 class='section'>Example:</h5> 326 * <p class='bjava'> 327 * <jc>// Asserts that the specified method throws an exception with a 328 * // caused-by RuntimeException containing 'foobar'</jc> 329 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 330 * .findCausedBy(RuntimeException.<jk>class</jk>) 331 * .isExists() 332 * .asMessage() 333 * .isPattern(<js>"foobar"</js>); 334 * </p> 335 * 336 * @param <X> The throwable type. 337 * @param throwableClass The class type to search for in the caused-by chain. 338 * @return An assertion against the caused-by throwable. Never <jk>null</jk>. 339 */ 340 public <X extends Throwable> FluentThrowableAssertion<X,R> asFind(Class<X> throwableClass) { 341 Throwable t = orElse(null); 342 while (t != null) { 343 if (throwableClass.isInstance(t)) 344 return new FluentThrowableAssertion<>(this, throwableClass.cast(t), returns()); 345 t = t.getCause(); 346 } 347 return new FluentThrowableAssertion<>(this, (X)null, returns()); 348 } 349 350 //----------------------------------------------------------------------------------------------------------------- 351 // Test methods 352 //----------------------------------------------------------------------------------------------------------------- 353 354 /** 355 * Asserts that this throwable is of the specified type. 356 * 357 * <h5 class='section'>Example:</h5> 358 * <p class='bjava'> 359 * <jc>// Asserts that the specified method throws a RuntimeException. </jc> 360 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 361 * .isType(RuntimeException.<jk>class</jk>); 362 * </p> 363 * 364 * @param parent The type. 365 * @return The fluent return object. 366 */ 367 @Override 368 public R isType(Class<?> parent) { 369 assertArgNotNull("parent", parent); 370 if (! parent.isInstance(value())) 371 throw error(MSG_exceptionWasNotExpectedType, className(parent), className(value())); 372 return returns(); 373 } 374 375 /** 376 * Asserts that this throwable is exactly the specified type. 377 * 378 * <h5 class='section'>Example:</h5> 379 * <p class='bjava'> 380 * <jc>// Asserts that the specified method throws a RuntimeException. </jc> 381 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()) 382 * .isExactType(RuntimeException.<jk>class</jk>); 383 * </p> 384 * 385 * @param type The type. 386 * @return The fluent return object. 387 */ 388 @Override 389 public R isExactType(Class<?> type) { 390 assertArgNotNull("type", type); 391 if (type != value().getClass()) 392 throw error(MSG_exceptionWasNotExpectedType, className(type), className(value())); 393 return returns(); 394 } 395 396 /** 397 * Asserts that this throwable exists. 398 * 399 * <h5 class='section'>Example:</h5> 400 * <p class='bjava'> 401 * <jc>// Asserts that the specified method throws any exception.</jc> 402 * ThrowableAssertion.<jsm>assertThrown</jsm>(() -> <jv>foo</jv>.getBar()).isExists(); 403 * </p> 404 * 405 * @return The fluent return object. 406 */ 407 @Override 408 public R isExists() { 409 if (valueIsNull()) 410 throw error(MSG_exceptionWasNotThrown); 411 return returns(); 412 } 413 414 //----------------------------------------------------------------------------------------------------------------- 415 // Fluent setters 416 //----------------------------------------------------------------------------------------------------------------- 417 418 // <FluentSetters> 419 420 @Override /* GENERATED - org.apache.juneau.assertions.Assertion */ 421 public FluentThrowableAssertion<T,R> setMsg(String msg, Object...args) { 422 super.setMsg(msg, args); 423 return this; 424 } 425 426 @Override /* GENERATED - org.apache.juneau.assertions.Assertion */ 427 public FluentThrowableAssertion<T,R> setOut(PrintStream value) { 428 super.setOut(value); 429 return this; 430 } 431 432 @Override /* GENERATED - org.apache.juneau.assertions.Assertion */ 433 public FluentThrowableAssertion<T,R> setSilent() { 434 super.setSilent(); 435 return this; 436 } 437 438 @Override /* GENERATED - org.apache.juneau.assertions.Assertion */ 439 public FluentThrowableAssertion<T,R> setStdOut() { 440 super.setStdOut(); 441 return this; 442 } 443 444 @Override /* GENERATED - org.apache.juneau.assertions.Assertion */ 445 public FluentThrowableAssertion<T,R> setThrowable(Class<? extends java.lang.RuntimeException> value) { 446 super.setThrowable(value); 447 return this; 448 } 449 450 // </FluentSetters> 451 452 //----------------------------------------------------------------------------------------------------------------- 453 // Utility methods 454 //----------------------------------------------------------------------------------------------------------------- 455 456 @Override 457 protected boolean equals(Object o1, Object o2) { 458 if (o1 instanceof Throwable && o2 instanceof Throwable) 459 return ObjectUtils.eq((Throwable)o1, (Throwable)o2, (x,y)->ObjectUtils.eq(x.getClass(),y.getClass()) && ObjectUtils.eq(x.getMessage(),y.getMessage())); 460 return super.equals(o1, o2); 461 } 462}