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 org.apache.juneau.common.internal.ArgUtils.*;
016import static org.apache.juneau.common.internal.StringUtils.*;
017import static org.apache.juneau.common.internal.ThrowableUtils.*;
018import static org.apache.juneau.internal.CollectionUtils.*;
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.json.*;
027import org.apache.juneau.reflect.*;
028import org.apache.juneau.serializer.*;
029
030/**
031 * Used for fluent assertion calls against POJOs.
032 *
033 * <h5 class='section'>Test Methods:</h5>
034 * <p>
035 * <ul class='javatree'>
036 *    <li class='jc'>{@link FluentObjectAssertion}
037 *    <ul class='javatreec'>
038 *       <li class='jm'>{@link FluentObjectAssertion#isExists() isExists()}
039 *       <li class='jm'>{@link FluentObjectAssertion#is(Object) is(Object)}
040 *       <li class='jm'>{@link FluentObjectAssertion#is(Predicate) is(Predicate)}
041 *       <li class='jm'>{@link FluentObjectAssertion#isNot(Object) isNot(Object)}
042 *       <li class='jm'>{@link FluentObjectAssertion#isAny(Object...) isAny(Object...)}
043 *       <li class='jm'>{@link FluentObjectAssertion#isNotAny(Object...) isNotAny(Object...)}
044 *       <li class='jm'>{@link FluentObjectAssertion#isNull() isNull()}
045 *       <li class='jm'>{@link FluentObjectAssertion#isNotNull() isNotNull()}
046 *       <li class='jm'>{@link FluentObjectAssertion#isString(String) isString(String)}
047 *       <li class='jm'>{@link FluentObjectAssertion#isJson(String) isJson(String)}
048 *       <li class='jm'>{@link FluentObjectAssertion#isSame(Object) isSame(Object)}
049 *       <li class='jm'>{@link FluentObjectAssertion#isSameJsonAs(Object) isSameJsonAs(Object)}
050 *       <li class='jm'>{@link FluentObjectAssertion#isSameSortedJsonAs(Object) isSameSortedJsonAs(Object)}
051 *       <li class='jm'>{@link FluentObjectAssertion#isSameSerializedAs(Object, WriterSerializer) isSameSerializedAs(Object, WriterSerializer)}
052 *       <li class='jm'>{@link FluentObjectAssertion#isType(Class) isType(Class)}
053 *       <li class='jm'>{@link FluentObjectAssertion#isExactType(Class) isExactType(Class)}
054 *    </ul>
055 * </ul>
056 *
057 * <h5 class='section'>Transform Methods:</h5>
058 * <p>
059 * <ul class='javatree'>
060 *    <li class='jc'>{@link FluentObjectAssertion}
061 *    <ul class='javatreec'>
062 *       <li class='jm'>{@link FluentObjectAssertion#asString() asString()}
063 *       <li class='jm'>{@link FluentObjectAssertion#asString(WriterSerializer) asString(WriterSerializer)}
064 *       <li class='jm'>{@link FluentObjectAssertion#asString(Function) asString(Function)}
065 *       <li class='jm'>{@link FluentObjectAssertion#asJson() asJson()}
066 *       <li class='jm'>{@link FluentObjectAssertion#asJsonSorted() asJsonSorted()}
067 *       <li class='jm'>{@link FluentObjectAssertion#asTransformed(Function) asApplied(Function)}
068 *       <li class='jm'>{@link FluentObjectAssertion#asAny() asAny()}
069 * </ul>
070 * </ul>
071 *
072 * <h5 class='section'>Configuration Methods:</h5>
073 * <p>
074 * <ul class='javatree'>
075 *    <li class='jc'>{@link Assertion}
076 *    <ul class='javatreec'>
077 *       <li class='jm'>{@link Assertion#setMsg(String, Object...) setMsg(String, Object...)}
078 *       <li class='jm'>{@link Assertion#setOut(PrintStream) setOut(PrintStream)}
079 *       <li class='jm'>{@link Assertion#setSilent() setSilent()}
080 *       <li class='jm'>{@link Assertion#setStdOut() setStdOut()}
081 *       <li class='jm'>{@link Assertion#setThrowable(Class) setThrowable(Class)}
082 *    </ul>
083 * </ul>
084 *
085 * <h5 class='section'>See Also:</h5><ul>
086 *    <li class='link'><a class="doclink" href="../../../../index.html#ja.Overview">Overview &gt; juneau-assertions &gt; Overview</a>
087 * </ul>
088 *
089 * @param <T> The object type.
090 * @param <R> The return type.
091 */
092@FluentSetters(returns="FluentObjectAssertion<T,R>")
093public class FluentObjectAssertion<T,R> extends FluentAssertion<R> {
094
095   //-----------------------------------------------------------------------------------------------------------------
096   // Static
097   //-----------------------------------------------------------------------------------------------------------------
098
099   private static final Messages MESSAGES = Messages.of(FluentObjectAssertion.class, "Messages");
100   private static final String
101      MSG_unexpectedType = MESSAGES.getString("unexpectedType"),
102      MSG_unexpectedComparison = MESSAGES.getString("unexpectedComparison"),
103      MSG_unexpectedValue = MESSAGES.getString("unexpectedValue"),
104      MSG_unexpectedValueDidNotExpect = MESSAGES.getString("unexpectedValueDidNotExpect"),
105      MSG_notTheSameValue = MESSAGES.getString("notTheSameValue"),
106      MSG_valueWasNull = MESSAGES.getString("valueWasNull"),
107      MSG_valueWasNotNull = MESSAGES.getString("valueWasNotNull"),
108      MSG_expectedValueNotFound = MESSAGES.getString("expectedValueNotFound"),
109      MSG_unexpectedValueFound = MESSAGES.getString("unexpectedValueFound"),
110      MSG_unexpectedValue2 = MESSAGES.getString("unexpectedValue2");
111
112   private static JsonSerializer JSON = JsonSerializer.create()
113      .json5()
114      .build();
115
116   private static JsonSerializer JSON_SORTED = JsonSerializer.create()
117      .json5()
118      .sortProperties()
119      .sortCollections()
120      .sortMaps()
121      .build();
122
123   //-----------------------------------------------------------------------------------------------------------------
124   // Instance
125   //-----------------------------------------------------------------------------------------------------------------
126
127   private final T value;
128
129   /**
130    * Constructor.
131    *
132    * @param value
133    *    The object being tested.
134    *    <br>Can be <jk>null</jk>.
135    * @param returns
136    *    The object to return after a test method is called.
137    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
138    * used on the same assertion.
139    */
140   public FluentObjectAssertion(T value, R returns) {
141      this(null, value, returns);
142   }
143
144   /**
145    * Chained constructor.
146    *
147    * <p>
148    * Used when transforming one assertion into another so that the assertion config can be used by the new assertion.
149    *
150    * @param creator
151    *    The assertion that created this assertion.
152    *    <br>Should be <jk>null</jk> if this is the top-level assertion.
153    * @param value
154    *    The object being tested.
155    *    <br>Can be <jk>null</jk>.
156    * @param returns
157    *    The object to return after a test method is called.
158    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
159    * used on the same assertion.
160    */
161   public FluentObjectAssertion(Assertion creator, T value, R returns) {
162      super(creator, returns);
163      this.value = value;
164   }
165
166   //-----------------------------------------------------------------------------------------------------------------
167   // Transform methods
168   //-----------------------------------------------------------------------------------------------------------------
169
170   /**
171    * Converts this object to a string using {@link Object#toString} and returns it as a new assertion.
172    *
173    * <h5 class='section'>Example:</h5>
174    * <p class='bjava'>
175    *    <jc>// Validates that the specified object is "foobar" after converting to a string.</jc>
176    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
177    *       .asString()
178    *       .is(<js>"foobar"</js>);
179    * </p>
180    *
181    * @return A new fluent string assertion.
182    */
183   public FluentStringAssertion<R> asString() {
184      return new FluentStringAssertion<>(this, valueAsString(), returns());
185   }
186
187   /**
188    * Converts this object to text using the specified serializer and returns it as a new assertion.
189    *
190    * <h5 class='section'>Example:</h5>
191    * <p class='bjava'>
192    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
193    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
194    *       .asString(XmlSerializer.<jsf>DEFAULT</jsf>)
195    *       .is(<js>"&lt;object&gt;&lt;foo&gt;bar&lt;/foo&gt;&lt;baz&gt;qux&lt;/baz&gt;&lt;/object&gt;"</js>);
196    * </p>
197    *
198    * @param ws The serializer to use to convert the object to text.
199    * @return A new fluent string assertion.
200    */
201   public FluentStringAssertion<R> asString(WriterSerializer ws) {
202      try {
203         return new FluentStringAssertion<>(this, ws.serialize(value), returns());
204      } catch (SerializeException e) {
205         throw asRuntimeException(e);
206      }
207   }
208
209   /**
210    * Converts this object to a string using the specified function and returns it as a new assertion.
211    *
212    * <h5 class='section'>Example:</h5>
213    * <p class='bjava'>
214    *    <jc>// Validates that the specified object is "foobar" after converting to a string.</jc>
215    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
216    *       .asString(<jv>x</jv>-&gt;<jv>x</jv>.toString())
217    *       .is(<js>"foobar"</js>);
218    * </p>
219    *
220    * @param function The conversion function.
221    * @return A new fluent string assertion.
222    */
223   public FluentStringAssertion<R> asString(Function<T,String> function) {
224      return new FluentStringAssertion<>(this, function.apply(value), returns());
225   }
226
227   /**
228    * Converts this object to simplified JSON and returns it as a new assertion.
229    *
230    * <h5 class='section'>Example:</h5>
231    * <p class='bjava'>
232    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
233    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
234    *       .asJson()
235    *       .is(<js>"{foo:'bar',baz:'qux'}"</js>);
236    * </p>
237    *
238    * @return A new fluent string assertion.
239    */
240   public FluentStringAssertion<R> asJson() {
241      return asString(JSON);
242   }
243
244   /**
245    * Converts this object to sorted simplified JSON and returns it as a new assertion.
246    *
247    * <h5 class='section'>Example:</h5>
248    * <p class='bjava'>
249    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
250    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
251    *       .asJsonSorted()
252    *       .is(<js>"{baz:'qux',foo:'bar'}"</js>);
253    * </p>
254    *
255    * @return A new fluent string assertion.
256    */
257   public FluentStringAssertion<R> asJsonSorted() {
258      return asString(JSON_SORTED);
259   }
260
261   /**
262    * Applies a transform on the inner object and returns a new inner object.
263    *
264    * @param function The function to apply.
265    * @return This object.
266    */
267   public FluentObjectAssertion<T,R> asTransformed(Function<T,T> function) {
268      return new FluentObjectAssertion<>(this, function.apply(orElse(null)), returns());
269   }
270
271   /**
272    * Applies a transform on the inner object and returns a new inner object.
273    *
274    * @param <T2> The transform-to type.
275    * @param function The function to apply.
276    * @return This object.
277    */
278   public <T2> FluentObjectAssertion<T2,R> asTransformedTo(Function<T,T2> function) {
279      return new FluentObjectAssertion<>(this, function.apply(orElse(null)), returns());
280   }
281
282   /**
283    * Converts this assertion into an {@link FluentAnyAssertion} so that it can be converted to other assertion types.
284    *
285    * @return This object.
286    */
287   public FluentAnyAssertion<T,R> asAny() {
288      return new FluentAnyAssertion<>(this, orElse(null), returns());
289   }
290
291   //-----------------------------------------------------------------------------------------------------------------
292   // Test methods
293   //-----------------------------------------------------------------------------------------------------------------
294
295   /**
296    * Asserts that the object is not null.
297    *
298    * <p>
299    * Equivalent to {@link #isNotNull()}.
300    *
301    * @return The fluent return object.
302    * @throws AssertionError If assertion failed.
303    */
304   public R isExists() throws AssertionError {
305      return isNotNull();
306   }
307
308   /**
309    * Asserts that the object i null.
310    *
311    * <p>
312    * Equivalent to {@link #isNotNull()}.
313    *
314    * @return The fluent return object.
315    * @throws AssertionError If assertion failed.
316    */
317   public R isNull() throws AssertionError {
318      if (value != null)
319         throw error(MSG_valueWasNotNull);
320      return returns();
321   }
322
323   /**
324    * Asserts that the object is not null.
325    *
326    * <p>
327    * Equivalent to {@link #isNotNull()}.
328    *
329    * @return The fluent return object.
330    * @throws AssertionError If assertion failed.
331    */
332   public R isNotNull() throws AssertionError {
333      if (value == null)
334         throw error(MSG_valueWasNull);
335      return returns();
336   }
337
338   /**
339    * Asserts that the value equals the specified value.
340    *
341    * @param value The value to check against.
342    * @return The fluent return object.
343    * @throws AssertionError If assertion failed.
344    */
345   public R is(T value) throws AssertionError {
346      if (this.value == value)
347         return returns();
348      if (! equals(orElse(null), value))
349         throw error(MSG_unexpectedValue, value, this.value);
350      return returns();
351   }
352
353   /**
354    * Asserts that the value converted to a string equals the specified value.
355    *
356    * @param value The value to check against.
357    * @return The fluent return object.
358    * @throws AssertionError If assertion failed.
359    */
360   public R isString(String value) {
361      return asString().is(value);
362   }
363
364   /**
365    * Asserts that the value does not equal the specified value.
366    *
367    * @param value The value to check against.
368    * @return The fluent return object.
369    * @throws AssertionError If assertion failed.
370    */
371   public R isNot(T value) throws AssertionError {
372      if (equals(orElse(null), value))
373         throw error(MSG_unexpectedValueDidNotExpect, value, orElse(null));
374      return returns();
375   }
376
377   /**
378    * Asserts that the value is one of the specified values.
379    *
380    * @param values The values to check against.
381    * @return The fluent return object.
382    * @throws AssertionError If assertion failed.
383    */
384   @SuppressWarnings("unchecked")
385   public R isAny(T...values) throws AssertionError {
386      for (T v : values)
387         if (equals(orElse(null), v))
388            return returns();
389      throw error(MSG_expectedValueNotFound, values, value);
390   }
391
392   /**
393    * Asserts that the value is not one of the specified values.
394    *
395    * @param values The values to check against.
396    * @return The fluent return object.
397    * @throws AssertionError If assertion failed.
398    */
399   @SuppressWarnings("unchecked")
400   public R isNotAny(T...values) throws AssertionError {
401      for (T v : values)
402         if (equals(orElse(null), v))
403            throw error(MSG_unexpectedValueFound, v, value);
404      return returns();
405   }
406
407   /**
408    * Asserts that the specified object is the same object as this object.
409    *
410    * @param value The value to check against.
411    * @return The fluent return object.
412    * @throws AssertionError If assertion failed.
413    */
414   public R isSame(T value) throws AssertionError {
415      if (this.value == value)
416         return returns();
417      throw error(MSG_notTheSameValue, value, ObjectUtils.identity(value), this.value, ObjectUtils.identity(this.value));
418   }
419
420   /**
421    * Verifies that two objects are equivalent after converting them both to JSON.
422    *
423    * @param o The object to compare against.
424    * @return The fluent return object.
425    * @throws AssertionError If assertion failed.
426    */
427   public R isSameJsonAs(Object o) throws AssertionError {
428      return isSameSerializedAs(o, JSON);
429   }
430
431   /**
432    * Verifies that two objects are equivalent after converting them both to sorted JSON.
433    *
434    * <p>
435    * Properties, maps, and collections are all sorted on both objects before comparison.
436    *
437    * @param o The object to compare against.
438    * @return The fluent return object.
439    * @throws AssertionError If assertion failed.
440    */
441   public R isSameSortedJsonAs(Object o) {
442      return isSameSerializedAs(o, JSON_SORTED);
443   }
444
445   /**
446    * Asserts that the specified object is the same as this object after converting both to strings using the specified serializer.
447    *
448    * @param o The object to compare against.
449    * @param serializer The serializer to use to serialize this object.
450    * @return The fluent return object.
451    * @throws AssertionError If assertion failed.
452    */
453   public R isSameSerializedAs(Object o, WriterSerializer serializer) {
454      String s1 = serializer.toString(value);
455      String s2 = serializer.toString(o);
456      if (ne(s1, s2))
457         throw error(MSG_unexpectedComparison, s2, s1);
458      return returns();
459   }
460
461   /**
462    * Asserts that the object is an instance of the specified class.
463    *
464    * <h5 class='section'>Example:</h5>
465    * <p class='bjava'>
466    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
467    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>).isType(MyBean.<jk>class</jk>);
468    * </p>
469    *
470    * @param parent The value to check against.
471    * @return The fluent return object.
472    * @throws AssertionError If assertion failed.
473    */
474   public R isType(Class<?> parent) throws AssertionError {
475      assertArgNotNull("parent", parent);
476      if (! ClassInfo.of(value()).isChildOf(parent))
477         throw error(MSG_unexpectedType, className(parent), className(value));
478      return returns();
479   }
480
481   /**
482    * Asserts that the object is an instance of the specified class.
483    *
484    * <h5 class='section'>Example:</h5>
485    * <p class='bjava'>
486    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
487    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>).isExactType(MyBean.<jk>class</jk>);
488    * </p>
489    *
490    * @param type The value to check against.
491    * @return The fluent return object.
492    * @throws AssertionError If assertion failed.
493    */
494   public R isExactType(Class<?> type) throws AssertionError {
495      assertArgNotNull("parent", type);
496      if (value().getClass() != type)
497         throw error(MSG_unexpectedType, className(type), className(value));
498      return returns();
499   }
500
501   /**
502    * Asserts that the value passes the specified predicate test.
503    *
504    * @param test The predicate to use to test the value.
505    * @return The fluent return object.
506    * @throws AssertionError If assertion failed.
507    */
508   public R is(Predicate<T> test) throws AssertionError {
509      if (test != null && ! test.test(value))
510         throw error(getFailureMessage(test, value));
511      return returns();
512   }
513
514   /**
515    * Converts this object to simplified JSON and runs the {@link FluentStringAssertion#is(String)} on the result.
516    *
517    * <h5 class='section'>Example:</h5>
518    * <p class='bjava'>
519    *    <jc>// Validates that the specified object is an instance of MyBean.</jc>
520    *    <jsm>assertObject</jsm>(<jv>myPojo</jv>)
521    *       .asJson()
522    *       .is(<js>"{foo:'bar',baz:'qux'}"</js>);
523    * </p>
524    *
525    * @param value The expected string value.
526    * @return The fluent return object.
527    */
528   public R isJson(String value) {
529      return asJson().is(value);
530   }
531
532   //-----------------------------------------------------------------------------------------------------------------
533   // Fluent setters
534   //-----------------------------------------------------------------------------------------------------------------
535
536   // <FluentSetters>
537
538   @Override /* GENERATED - org.apache.juneau.assertions.Assertion */
539   public FluentObjectAssertion<T,R> setMsg(String msg, Object...args) {
540      super.setMsg(msg, args);
541      return this;
542   }
543
544   @Override /* GENERATED - org.apache.juneau.assertions.Assertion */
545   public FluentObjectAssertion<T,R> setOut(PrintStream value) {
546      super.setOut(value);
547      return this;
548   }
549
550   @Override /* GENERATED - org.apache.juneau.assertions.Assertion */
551   public FluentObjectAssertion<T,R> setSilent() {
552      super.setSilent();
553      return this;
554   }
555
556   @Override /* GENERATED - org.apache.juneau.assertions.Assertion */
557   public FluentObjectAssertion<T,R> setStdOut() {
558      super.setStdOut();
559      return this;
560   }
561
562   @Override /* GENERATED - org.apache.juneau.assertions.Assertion */
563   public FluentObjectAssertion<T,R> setThrowable(Class<? extends java.lang.RuntimeException> value) {
564      super.setThrowable(value);
565      return this;
566   }
567
568   // </FluentSetters>
569
570   //-----------------------------------------------------------------------------------------------------------------
571   // Utility methods
572   //-----------------------------------------------------------------------------------------------------------------
573
574   /**
575    * Returns the inner value after asserting it is not <jk>null</jk>.
576    *
577    * @return The inner value.
578    * @throws AssertionError If inner value was <jk>null</jk>.
579    */
580   protected T value() throws AssertionError {
581      isExists();
582      return value;
583   }
584
585   /**
586    * Returns the inner value as a string.
587    *
588    * @return The inner value as a string, or <jk>null</jk> if the value was null.
589    */
590   protected String valueAsString() {
591      return stringify(value);
592   }
593
594   /**
595    * Returns the inner value or the other value if the value is <jk>null</jk>.
596    *
597    * @param other The other value.
598    * @return The inner value.
599    */
600   protected T orElse(T other) {
601      return value == null ? other : value;
602   }
603
604   /**
605    * Returns <jk>true</jk> if the inner value is null.
606    *
607    * @return <jk>true</jk> if the inner value is null.
608    */
609   protected boolean valueIsNull() {
610      return value == null;
611   }
612
613   /**
614    * Returns <jk>true</jk> if the inner value is not null.
615    *
616    * @return <jk>true</jk> if the inner value is not null.
617    */
618   protected boolean valueIsNotNull() {
619      return value != null;
620   }
621
622   /**
623    * Returns the value wrapped in an {@link Optional}.
624    *
625    * @return The value wrapped in an {@link Optional}.
626    */
627   protected Optional<T> opt() {
628      return optional(value);
629   }
630
631   /**
632    * Returns the result of running the specified function against the value and returns the result.
633    *
634    * @param <T2> The mapper-to type.
635    * @param mapper The function to run against the value.
636    * @return The result, never <jk>null</jk>.
637    */
638   protected <T2> Optional<T2> map(Function<? super T, ? extends T2> mapper) {
639      return opt().map(mapper);
640   }
641
642   /**
643    * Returns the predicate failure message.
644    *
645    * <p>
646    * If the predicate extends from {@link AssertionPredicate}, then the message comes from {@link AssertionPredicate#getFailureMessage()}.
647    * Otherwise, returns a generic <js>"Unexpected value: x"</js> message.
648    *
649    * @param p The function to run against the value.
650    * @param value The value that failed the test.
651    * @return The result, never <jk>null</jk>.
652    */
653   protected String getFailureMessage(Predicate<?> p, Object value) {
654      if (p instanceof AssertionPredicate)
655         return ((AssertionPredicate<?>)p).getFailureMessage();
656      return format(MSG_unexpectedValue2, value);
657   }
658
659   /**
660    * Checks two objects for equality.
661    *
662    * @param o1 The first object.
663    * @param o2 The second object.
664    * @return <jk>true</jk> if the objects are equal.
665    */
666   protected boolean equals(Object o1, Object o2) {
667      if (o1 == o2)
668         return true;
669      if (o1 == null || o2 == null)
670         return false;
671      if (o1.equals(o2))
672         return true;
673      if (o1.getClass().isArray())
674         return stringifyDeep(o1).equals(stringifyDeep(o2));
675      return false;
676   }
677
678   /**
679    * Returns the string form of the inner object.
680    * Subclasses can override this method to affect the {@link #asString()} method (and related).
681    */
682   @Override /* Object */
683   public String toString() {
684      return valueAsString();
685   }
686}