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 java.util.Collections.*;
020import static java.util.stream.Collectors.*;
021import static java.util.Arrays.*;
022
023import java.io.*;
024import java.util.*;
025import java.util.function.*;
026
027import org.apache.juneau.cp.*;
028import org.apache.juneau.internal.*;
029import org.apache.juneau.serializer.*;
030
031/**
032 * Used for fluent assertion calls against maps.
033 *
034 * <h5 class='section'>Test Methods:</h5>
035 * <p>
036 * <ul class='javatree'>
037 *    <li class='jc'>{@link FluentMapAssertion}
038 *    <ul class='javatreec'>
039 *       <li class='jm'>{@link FluentMapAssertion#isEmpty() isEmpty()}
040 *       <li class='jm'>{@link FluentMapAssertion#isNotEmpty() isNotEmpty()}
041 *       <li class='jm'>{@link FluentMapAssertion#isContainsKey(String) isContainsKey(String)}
042 *       <li class='jm'>{@link FluentMapAssertion#isNotContainsKey(String) isNotContainsKey(String)}
043 *       <li class='jm'>{@link FluentMapAssertion#isSize(int) isSize(int)}
044 *    </ul>
045 *    <li class='jc'>{@link FluentObjectAssertion}
046 *    <ul class='javatreec'>
047 *       <li class='jm'>{@link FluentObjectAssertion#isExists() isExists()}
048 *       <li class='jm'>{@link FluentObjectAssertion#is(Object) is(Object)}
049 *       <li class='jm'>{@link FluentObjectAssertion#is(Predicate) is(Predicate)}
050 *       <li class='jm'>{@link FluentObjectAssertion#isNot(Object) isNot(Object)}
051 *       <li class='jm'>{@link FluentObjectAssertion#isAny(Object...) isAny(Object...)}
052 *       <li class='jm'>{@link FluentObjectAssertion#isNotAny(Object...) isNotAny(Object...)}
053 *       <li class='jm'>{@link FluentObjectAssertion#isNull() isNull()}
054 *       <li class='jm'>{@link FluentObjectAssertion#isNotNull() isNotNull()}
055 *       <li class='jm'>{@link FluentObjectAssertion#isString(String) isString(String)}
056 *       <li class='jm'>{@link FluentObjectAssertion#isJson(String) isJson(String)}
057 *       <li class='jm'>{@link FluentObjectAssertion#isSame(Object) isSame(Object)}
058 *       <li class='jm'>{@link FluentObjectAssertion#isSameJsonAs(Object) isSameJsonAs(Object)}
059 *       <li class='jm'>{@link FluentObjectAssertion#isSameSortedJsonAs(Object) isSameSortedJsonAs(Object)}
060 *       <li class='jm'>{@link FluentObjectAssertion#isSameSerializedAs(Object, WriterSerializer) isSameSerializedAs(Object, WriterSerializer)}
061 *       <li class='jm'>{@link FluentObjectAssertion#isType(Class) isType(Class)}
062 *       <li class='jm'>{@link FluentObjectAssertion#isExactType(Class) isExactType(Class)}
063 *    </ul>
064 * </ul>
065 *
066 * <h5 class='section'>Transform Methods:</h5>
067 * <p>
068 * <ul class='javatree'>
069 *    <li class='jc'>{@link FluentMapAssertion}
070 *    <ul class='javatreec'>
071 *       <li class='jm'>{@link FluentMapAssertion#asValue(Object) asValue(Object)}
072 *       <li class='jm'>{@link FluentMapAssertion#asValues(Object...) asValues(Object...)}
073 *       <li class='jm'>{@link FluentMapAssertion#asValueMap(Object...) asValueMap(Object...)}
074 *       <li class='jm'>{@link FluentMapAssertion#asSize() asSize()}
075 *    </ul>
076 *    <li class='jc'>{@link FluentObjectAssertion}
077 *    <ul class='javatreec'>
078 *       <li class='jm'>{@link FluentObjectAssertion#asString() asString()}
079 *       <li class='jm'>{@link FluentObjectAssertion#asString(WriterSerializer) asString(WriterSerializer)}
080 *       <li class='jm'>{@link FluentObjectAssertion#asString(Function) asString(Function)}
081 *       <li class='jm'>{@link FluentObjectAssertion#asJson() asJson()}
082 *       <li class='jm'>{@link FluentObjectAssertion#asJsonSorted() asJsonSorted()}
083 *       <li class='jm'>{@link FluentObjectAssertion#asTransformed(Function) asApplied(Function)}
084 *       <li class='jm'>{@link FluentObjectAssertion#asAny() asAny()}
085 * </ul>
086 * </ul>
087 *
088 * <h5 class='section'>Configuration Methods:</h5>
089 * <p>
090 * <ul class='javatree'>
091 *    <li class='jc'>{@link Assertion}
092 *    <ul class='javatreec'>
093 *       <li class='jm'>{@link Assertion#setMsg(String, Object...) setMsg(String, Object...)}
094 *       <li class='jm'>{@link Assertion#setOut(PrintStream) setOut(PrintStream)}
095 *       <li class='jm'>{@link Assertion#setSilent() setSilent()}
096 *       <li class='jm'>{@link Assertion#setStdOut() setStdOut()}
097 *       <li class='jm'>{@link Assertion#setThrowable(Class) setThrowable(Class)}
098 *    </ul>
099 * </ul>
100 *
101 * <h5 class='section'>See Also:</h5><ul>
102 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauEcosystemOverview">Juneau Ecosystem Overview</a>
103 * </ul>
104 *
105 * @param <K> The key type.
106 * @param <V> The value type.
107 * @param <R> The return type.
108 */
109public class FluentMapAssertion<K,V,R> extends FluentObjectAssertion<Map<K,V>,R>  {
110
111   //-----------------------------------------------------------------------------------------------------------------
112   // Static
113   //-----------------------------------------------------------------------------------------------------------------
114
115   private static final Messages MESSAGES = Messages.of(FluentMapAssertion.class, "Messages");
116   private static final String
117      MSG_mapWasNotEmpty = MESSAGES.getString("mapWasNotEmpty"),
118      MSG_mapDidNotContainExpectedKey = MESSAGES.getString("mapDidNotContainExpectedKey"),
119      MSG_mapContainedUnexpectedKey = MESSAGES.getString("mapContainedUnexpectedKey"),
120      MSG_mapWasEmpty = MESSAGES.getString("mapWasEmpty"),
121      MSG_mapDidNotHaveTheExpectedSize = MESSAGES.getString("mapDidNotHaveTheExpectedSize");
122
123   //-----------------------------------------------------------------------------------------------------------------
124   // Instance
125   //-----------------------------------------------------------------------------------------------------------------
126
127   /**
128    * Constructor.
129    *
130    * @param value
131    *    The object being tested.
132    *    <br>Can be <jk>null</jk>.
133    * @param returns
134    *    The object to return after a test method is called.
135    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
136    * used on the same assertion.
137    */
138   public FluentMapAssertion(Map<K,V> value, R returns) {
139      this(null, value, returns);
140   }
141
142   /**
143    * Chained constructor.
144    *
145    * <p>
146    * Used when transforming one assertion into another so that the assertion config can be used by the new assertion.
147    *
148    * @param creator
149    *    The assertion that created this assertion.
150    *    <br>Should be <jk>null</jk> if this is the top-level assertion.
151    * @param value
152    *    The object being tested.
153    *    <br>Can be <jk>null</jk>.
154    * @param returns
155    *    The object to return after a test method is called.
156    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
157    * used on the same assertion.
158    */
159   public FluentMapAssertion(Assertion creator, Map<K,V> value, R returns) {
160      super(creator, value, returns);
161   }
162
163   //-----------------------------------------------------------------------------------------------------------------
164   // Transform methods
165   //-----------------------------------------------------------------------------------------------------------------
166
167   @Override /* FluentObjectAssertion */
168   public FluentMapAssertion<K,V,R> asTransformed(Function<Map<K,V>,Map<K,V>> function) {  // NOSONAR - Intentional.
169      return new FluentMapAssertion<>(this, function.apply(orElse(null)), returns());
170   }
171
172   /**
173    * Returns an object assertion on the value specified at the specified key.
174    *
175    * <p>
176    * If the map is <jk>null</jk> or the map doesn't contain the specified key, the returned assertion is a null assertion
177    * (meaning {@link FluentAnyAssertion#isExists()} returns <jk>false</jk>).
178    *
179    * @param key The key of the item to retrieve from the map.
180    * @return A new assertion.
181    */
182   public FluentAnyAssertion<V,R> asValue(K key) {
183      return new FluentAnyAssertion<>(this, get(key), returns());
184   }
185
186   /**
187    * Returns a {@link FluentListAssertion} of the values of the specified keys.
188    *
189    * If the map is <jk>null</jk>, the returned assertion is a null assertion
190    * (meaning {@link FluentObjectAssertion#isExists()} returns <jk>false</jk>).
191    *
192    * @param keys The keys of the values to retrieve from the map.
193    * @return A new assertion.
194    */
195   public FluentListAssertion<Object,R> asValues(K...keys) {
196      return new FluentListAssertion<>(this, valueIsNull() ? null : stream(keys).map(this::get).collect(toList()), returns());
197   }
198
199   /**
200    * Extracts a subset of this map.
201    *
202    * @param keys The entries to extract.
203    * @return This object.
204    */
205   public FluentMapAssertion<K,V,R> asValueMap(K...keys) {
206      if (valueIsNull())
207         return new FluentMapAssertion<>(this, null, returns());
208      Map<K,V> m1 = value(), m2 = CollectionUtils.map();
209      if (m1 != null)
210         for (var k : keys)
211            m2.put(k, m1.get(k));
212      return new FluentMapAssertion<>(this, m2, returns());
213   }
214
215   /**
216    * Returns an integer assertion on the size of this map.
217    *
218    * <p>
219    * If the map is <jk>null</jk>, the returned assertion is a null assertion
220    * (meaning {@link FluentIntegerAssertion#isExists()} returns <jk>false</jk>).
221    *
222    * @return A new assertion.
223    */
224   public FluentIntegerAssertion<R> asSize() {
225      return new FluentIntegerAssertion<>(this, valueIsNull() ? null : value().size(), returns());
226   }
227
228   //-----------------------------------------------------------------------------------------------------------------
229   // Test methods
230   //-----------------------------------------------------------------------------------------------------------------
231
232   /**
233    * Asserts that the map exists and is empty.
234    *
235    * @return The fluent return object.
236    * @throws AssertionError If assertion failed or value was <jk>null</jk>.
237    */
238   public R isEmpty() throws AssertionError {
239      if (! value().isEmpty())
240         throw error(MSG_mapWasNotEmpty);
241      return returns();
242   }
243
244   /**
245    * Asserts that the map exists and is not empty.
246    *
247    * @return The fluent return object.
248    * @throws AssertionError If assertion failed or value was <jk>null</jk>.
249    */
250   public R isNotEmpty() throws AssertionError {
251      if (value().isEmpty())
252         throw error(MSG_mapWasEmpty);
253      return returns();
254   }
255
256   /**
257    * Asserts that the map contains the expected key.
258    *
259    * @param name The key name to check for.
260    * @return The fluent return object.
261    * @throws AssertionError If assertion failed or value was <jk>null</jk>.
262    */
263   public R isContainsKey(String name) throws AssertionError {
264      if (value().containsKey(name))
265         return returns();
266      throw error(MSG_mapDidNotContainExpectedKey, name, value());
267   }
268
269   /**
270    * Asserts that the map contains the expected key.
271    *
272    * @param name The key name to check for.
273    * @return The fluent return object.
274    * @throws AssertionError If assertion failed or value was <jk>null</jk>.
275    */
276   public R isNotContainsKey(String name) throws AssertionError {
277      if (! value().containsKey(name))
278         return returns();
279      throw error(MSG_mapContainedUnexpectedKey, name, value());
280   }
281
282   /**
283    * Asserts that the map exists and is the specified size.
284    *
285    * @param size The expected size.
286    * @return The fluent return object.
287    * @throws AssertionError If assertion failed or value was <jk>null</jk>.
288    */
289   public R isSize(int size) throws AssertionError {
290      if (size2() != size)
291         throw error(MSG_mapDidNotHaveTheExpectedSize, size, size2());
292      return returns();
293   }
294
295   //-----------------------------------------------------------------------------------------------------------------
296   // Fluent setters
297   //-----------------------------------------------------------------------------------------------------------------
298   @Override /* Overridden from Assertion */
299   public FluentMapAssertion<K,V,R> setMsg(String msg, Object...args) {
300      super.setMsg(msg, args);
301      return this;
302   }
303
304   @Override /* Overridden from Assertion */
305   public FluentMapAssertion<K,V,R> setOut(PrintStream value) {
306      super.setOut(value);
307      return this;
308   }
309
310   @Override /* Overridden from Assertion */
311   public FluentMapAssertion<K,V,R> setSilent() {
312      super.setSilent();
313      return this;
314   }
315
316   @Override /* Overridden from Assertion */
317   public FluentMapAssertion<K,V,R> setStdOut() {
318      super.setStdOut();
319      return this;
320   }
321
322   @Override /* Overridden from Assertion */
323   public FluentMapAssertion<K,V,R> setThrowable(Class<? extends java.lang.RuntimeException> value) {
324      super.setThrowable(value);
325      return this;
326   }
327   //-----------------------------------------------------------------------------------------------------------------
328   // Utility methods
329   //-----------------------------------------------------------------------------------------------------------------
330
331   private V get(K key) {
332      return orElse(emptyMap()).get(key);
333   }
334
335   private int size2() {
336      return value().size();
337   }
338}