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 *      https://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.commons.lang3.builder;
018
019import java.util.Collection;
020import java.util.concurrent.atomic.AtomicBoolean;
021
022import org.apache.commons.lang3.ClassUtils;
023import org.apache.commons.lang3.mutable.MutableBoolean;
024
025/**
026 * Works with {@link ToStringBuilder} to create a "deep" {@code toString}.
027 *
028 * <p>To use this class write code as follows:</p>
029 *
030 * <pre>
031 * public class Job {
032 *   String title;
033 *   ...
034 * }
035 *
036 * public class Person {
037 *   String name;
038 *   int age;
039 *   boolean smoker;
040 *   Job job;
041 *
042 *   ...
043 *
044 *   public String toString() {
045 *     return new ReflectionToStringBuilder(this, new RecursiveToStringStyle()).toString();
046 *   }
047 * }
048 * </pre>
049 *
050 * <p>This will produce a toString of the format:
051 * {@code Person@7f54[name=Stephen,age=29,smoker=false,job=Job@43cd2[title=Manager]]}</p>
052 *
053 * @since 3.2
054 */
055public class RecursiveToStringStyle extends ToStringStyle {
056
057    /**
058     * Required for serialization support.
059     *
060     * @see java.io.Serializable
061     */
062    private static final long serialVersionUID = 1L;
063
064    /**
065     * Constructs a new instance.
066     */
067    public RecursiveToStringStyle() {
068    }
069
070    /**
071     * Tests whether or not to recursively format the given {@link Class}.
072     * <p>
073     * By default, this method always filters out the following:
074     * </p>
075     * <ul>
076     * <li><a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-5.html#jls-5.1.7">Boxed primitives</a>, see {@link ClassUtils#isPrimitiveWrapper(Class)}
077     * <li>{@link String}</li>
078     * <li>{@link Number} subclasses</li>
079     * <li>{@link AtomicBoolean}</li>
080     * <li>{@link MutableBoolean}</li>
081     * </ul>
082     *
083     * @param clazz The class to test.
084     * @return Whether or not to recursively format instances of the given {@link Class}.
085     */
086    protected boolean accept(final Class<?> clazz) {
087        // @formatter:off
088        return !ClassUtils.isPrimitiveWrapper(clazz) &&
089               !String.class.equals(clazz) &&
090               !Number.class.isAssignableFrom(clazz) &&
091               !AtomicBoolean.class.equals(clazz) &&
092               !MutableBoolean.class.equals(clazz);
093        // @formatter:on
094    }
095
096    @Override
097    protected void appendDetail(final StringBuffer buffer, final String fieldName, final Collection<?> coll) {
098        appendClassName(buffer, coll);
099        appendIdentityHashCode(buffer, coll);
100        appendDetail(buffer, fieldName, coll.toArray());
101    }
102
103    @Override
104    public void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) {
105        if (value != null && accept(value.getClass())) {
106            buffer.append(ReflectionToStringBuilder.toString(value, this));
107        } else {
108            super.appendDetail(buffer, fieldName, value);
109        }
110    }
111}