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.bct.assertions;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.io.*;
022import java.lang.reflect.*;
023
024import org.apache.juneau.commons.inject.*;
025import org.apache.juneau.commons.reflect.*;
026
027/**
028 * Base class for all assertion objects.
029 *
030 * <h5 class='section'>Test Methods:</h5>
031 * <p>
032 * <ul class='javatree'>
033 *    <li>None
034 * </ul>
035 *
036 * <h5 class='section'>Transform Methods:</h5>
037 * <p>
038 * <ul class='javatree'>
039 *    <li>None
040 * </ul>
041 *
042 * <h5 class='section'>Configuration Methods:</h5>
043 * <p>
044 * <ul class='javatree'>
045 *    <li class='jc'>{@link Assertion}
046 *    <ul class='javatreec'>
047 *       <li class='jm'>{@link Assertion#setMsg(String, Object...) setMsg(String, Object...)}
048 *       <li class='jm'>{@link Assertion#setOut(PrintStream) setOut(PrintStream)}
049 *       <li class='jm'>{@link Assertion#setSilent() setSilent()}
050 *       <li class='jm'>{@link Assertion#setStdOut() setStdOut()}
051 *       <li class='jm'>{@link Assertion#setThrowable(Class) setThrowable(Class)}
052 *    </ul>
053 * </ul>
054 *
055 * <ul class='seealso'>
056 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauEcosystemOverview">Juneau Ecosystem</a>
057 * </ul>
058 */
059@SuppressWarnings({
060   "java:S106", // System.out/err usage acceptable for assertion output
061   "java:S115", // Constants use UPPER_snakeCase convention (e.g., MSG_* fields); System.err/out usage is intentional for assertion error output
062   "resource" // out field assigned to System.err which is a JVM-managed singleton not owned by this class
063})
064public class Assertion {
065
066   /**
067    * Convenience method for getting the array class of the specified element type.
068    *
069    * @param <E> The element type.
070    * @param c The object to get the class name for.
071    * @return The class name for an object.
072    */
073   @SuppressWarnings({
074      "unchecked" // Type erasure requires cast to Class<E[]> for array class
075   })
076   protected static <E> Class<E[]> arrayClass(Class<E> c) {
077      return (Class<E[]>)Array.newInstance(c, 0).getClass();
078   }
079
080   private String msg;
081   private Object[] msgArgs;
082
083   private PrintStream out = System.err;
084
085   private Class<? extends RuntimeException> throwable;
086
087   /**
088    * Constructor used when this assertion is being created from within another assertion.
089    *
090    * @param creator The creator of this assertion.
091    */
092   protected Assertion(Assertion creator) {
093      if (nn(creator)) {
094         this.msg = creator.msg;
095         this.msgArgs = creator.msgArgs;
096         this.out = creator.out;
097         this.throwable = creator.throwable;
098      }
099   }
100
101   /**
102    * Allows you to override the assertion failure message.
103    *
104    * <p>
105    * String can contain <js>"{msg}"</js> to represent the original message.
106    *
107    * <h5 class='section'>Example:</h5>
108    * <p class='bjava'>
109    *    <jk>import static</jk> org.apache.juneau.bct.assertions.Assertions.*;
110    *
111    * <jc>// Throws an assertion with a custom message instead of the default "Value was null."</jc>
112    *    <jsm>assertString</jsm>(<jv>myString</jv>)
113    *       .setMsg(<js>"My string was bad:  {msg}"</js>)
114    *       .isNotNull();
115    * </p>
116    *
117    * @param msg The assertion failure message.
118    * @param args Optional message arguments.
119    * @return This object.
120    */
121   public Assertion setMsg(String msg, Object...args) {
122      this.msg = msg.replace("{msg}", "<<<MSG>>>");
123      this.msgArgs = args;
124      return this;
125   }
126
127   /**
128    * If an error occurs, send the error message to the specified stream instead of STDERR.
129    *
130    * @param value
131    *    The output stream.
132    *    Can be <jk>null</jk> to suppress output.
133    * @return This object.
134    */
135   public Assertion setOut(PrintStream value) {
136      out = value;
137      return this;
138   }
139
140   /**
141    * Suppresses output to STDERR.
142    *
143    * <p>
144    * This is the equivalent to calling <c>out(<jk>null</jk>)</c>.
145    *
146    * @return This object.
147    */
148   public Assertion setSilent() {
149      return setOut(null);
150   }
151
152   /**
153    * If an error occurs, send the error message to STDOUT instead of STDERR.
154    *
155    * @return This object.
156    */
157   public Assertion setStdOut() {
158      return setOut(System.out);
159   }
160
161   /**
162    * If an error occurs, throw this exception instead of the standard {@link AssertionError}.
163    *
164    * <p>
165    * The throwable class must have a public constructor that takes in any of the following parameters:
166    * <ul>
167    *    <li>{@link Throwable} - The caused-by exception (if there is one).
168    *    <li>{@link String} - The assertion failure message.
169    * </ul>
170    *
171    * <p>
172    * If the throwable cannot be instantiated, a {@link RuntimeException} is thrown instead.
173    *
174    * <h5 class='section'>Example:</h5>
175    * <p class='bjava'>
176    *    <jk>import static</jk> org.apache.juneau.bct.assertions.Assertions.*;
177    *
178    * <jc>// Throws a BadRequest instead of an AssertionError if the string is null.</jc>
179    *    <jsm>assertString</jsm>(<jv>myString</jv>)
180    *       .setThrowable(BadRequest.<jk>class</jk>)
181    *       .isNotNull();
182    * </p>
183    *
184    * @param value The new value for this setting.
185    * @return This object.
186    */
187   public Assertion setThrowable(Class<? extends RuntimeException> value) {
188      throwable = value;
189      return this;
190   }
191
192   /**
193    * Creates a new {@link BasicAssertionError}.
194    *
195    * @param msg The message.
196    * @param args The message arguments.
197    * @return A new {@link BasicAssertionError}.
198    */
199   protected BasicAssertionError error(String msg, Object...args) {
200      return error(null, msg, args);
201   }
202
203   /**
204    * Creates a new {@link BasicAssertionError}.
205    *
206    * @param cause Optional caused-by throwable.
207    * @param msg The message.
208    * @param args The message arguments.
209    * @return A new {@link BasicAssertionError}.
210    */
211   protected BasicAssertionError error(Throwable cause, String msg, Object...args) {
212      msg = f(msg, args);
213      if (nn(this.msg))
214         msg = f(this.msg, this.msgArgs).replace("<<<MSG>>>", msg);
215      if (nn(out))
216         out.println(msg);
217      if (nn(throwable)) {
218         try {
219            // @formatter:off
220            throw BeanInstantiator
221               .of(throwable)
222               .addBean(Throwable.class, cause)
223               .addBean(String.class, msg)
224               .addBean(Object[].class, new Object[0])
225               .run();
226         // @formatter:on
227         } catch (@SuppressWarnings("unused") ExecutableException e) {
228         // If we couldn't create requested exception, just throw a RuntimeException.
229            throw rex(cause, msg);
230         }
231      }
232      return new BasicAssertionError(cause, msg);
233   }
234}