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