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