    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 */
    017
    018package org.apache.commons.lang3.builder;
    019
    020import org.apache.commons.lang3.ClassUtils;
    021import org.apache.commons.lang3.StringUtils;
    022
    023/**
    024 * Works with {@link ToStringBuilder} to create a "deep" {@code toString}.
    025 * But instead a single line like the {@link RecursiveToStringStyle} this creates a multiline String
    026 * similar to the {@link ToStringStyle#MULTI_LINE_STYLE}.
    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 MultilineRecursiveToStringStyle()).toString();
    046 *   }
    047 * }
    048 * </pre>
    049 *
    050 * <p>
    051 * This will produce a toString of the format:<br>
    052 * {@code Person@7f54[ <br>
    053 * &nbsp; name=Stephen, <br>
    054 * &nbsp; age=29, <br>
    055 * &nbsp; smokealse, <br>
    056 * &nbsp; job=Job@43cd2[ <br>
    057 * &nbsp; &nbsp; title=Manager <br>
    058 * &nbsp;  ] <br>
    059 * ]
    060 * }
    061 * </p>
    062 *
    063 * @since 3.4
    064 */
    065public class MultilineRecursiveToStringStyle extends RecursiveToStringStyle {
    066
    067    /**
    068     * Required for serialization support.
    069     * @see java.io.Serializable
    070     */
    071    private static final long serialVersionUID = 1L;
    072
    073    /** Indenting of inner lines. */
    074    private static final int INDENT = 2;
    075
    076    /** Current indenting. */
    077    private int spaces = 2;
    078
    079    /**
    080     * Constructs a new instance.
    081     */
    082    public MultilineRecursiveToStringStyle() {
    083        resetIndent();
    084    }
    085
    086    @Override
    087    protected void appendDetail(final StringBuffer buffer, final String fieldName, final boolean[] array) {
    088        spaces += INDENT;
    089        resetIndent();
    090        super.appendDetail(buffer, fieldName, array);
    091        spaces -= INDENT;
    092        resetIndent();
    093    }
    094
    095    @Override
    096    protected void appendDetail(final StringBuffer buffer, final String fieldName, final byte[] array) {
    097        spaces += INDENT;
    098        resetIndent();
    099        super.appendDetail(buffer, fieldName, array);
    100        spaces -= INDENT;
    101        resetIndent();
    102    }
    103
    104    @Override
    105    protected void appendDetail(final StringBuffer buffer, final String fieldName, final char[] array) {
    106        spaces += INDENT;
    107        resetIndent();
    108        super.appendDetail(buffer, fieldName, array);
    109        spaces -= INDENT;
    110        resetIndent();
    111    }
    112
    113    @Override
    114    protected void appendDetail(final StringBuffer buffer, final String fieldName, final double[] array) {
    115        spaces += INDENT;
    116        resetIndent();
    117        super.appendDetail(buffer, fieldName, array);
    118        spaces -= INDENT;
    119        resetIndent();
    120    }
    121
    122    @Override
    123    protected void appendDetail(final StringBuffer buffer, final String fieldName, final float[] array) {
    124        spaces += INDENT;
    125        resetIndent();
    126        super.appendDetail(buffer, fieldName, array);
    127        spaces -= INDENT;
    128        resetIndent();
    129    }
    130
    131    @Override
    132    protected void appendDetail(final StringBuffer buffer, final String fieldName, final int[] array) {
    133        spaces += INDENT;
    134        resetIndent();
    135        super.appendDetail(buffer, fieldName, array);
    136        spaces -= INDENT;
    137        resetIndent();
    138    }
    139
    140    @Override
    141    protected void appendDetail(final StringBuffer buffer, final String fieldName, final long[] array) {
    142        spaces += INDENT;
    143        resetIndent();
    144        super.appendDetail(buffer, fieldName, array);
    145        spaces -= INDENT;
    146        resetIndent();
    147    }
    148
    149    @Override
    150    public void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) {
    151        if (!ClassUtils.isPrimitiveWrapper(value.getClass()) && !String.class.equals(value.getClass())
    152                && accept(value.getClass())) {
    153            spaces += INDENT;
    154            resetIndent();
    155            buffer.append(ReflectionToStringBuilder.toString(value, this));
    156            spaces -= INDENT;
    157            resetIndent();
    158        } else {
    159            super.appendDetail(buffer, fieldName, value);
    160        }
    161    }
    162
    163    @Override
    164    protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object[] array) {
    165        spaces += INDENT;
    166        resetIndent();
    167        super.appendDetail(buffer, fieldName, array);
    168        spaces -= INDENT;
    169        resetIndent();
    170    }
    171
    172    @Override
    173    protected void appendDetail(final StringBuffer buffer, final String fieldName, final short[] array) {
    174        spaces += INDENT;
    175        resetIndent();
    176        super.appendDetail(buffer, fieldName, array);
    177        spaces -= INDENT;
    178        resetIndent();
    179    }
    180
    181    @Override
    182    protected void reflectionAppendArrayDetail(final StringBuffer buffer, final String fieldName, final Object array) {
    183        spaces += INDENT;
    184        resetIndent();
    185        super.reflectionAppendArrayDetail(buffer, fieldName, array);
    186        spaces -= INDENT;
    187        resetIndent();
    188    }
    189
    190    /**
    191     * Resets the fields responsible for the line breaks and indenting.
    192     * Must be invoked after changing the {@link #spaces} value.
    193     */
    194    private void resetIndent() {
    195        setArrayStart("{" + System.lineSeparator() + spacer(spaces));
    196        setArraySeparator("," + System.lineSeparator() + spacer(spaces));
    197        setArrayEnd(System.lineSeparator() + spacer(spaces - INDENT) + "}");
    198
    199        setContentStart("[" + System.lineSeparator() + spacer(spaces));
    200        setFieldSeparator("," + System.lineSeparator() + spacer(spaces));
    201        setContentEnd(System.lineSeparator() + spacer(spaces - INDENT) + "]");
    202    }
    203
    204    /**
    205     * Creates a StringBuilder responsible for the indenting.
    206     *
    207     * @param spaces how far to indent
    208     * @return a StringBuilder with {spaces} leading space characters.
    209     */
    210    private String spacer(final int spaces) {
    211        return StringUtils.repeat(' ', spaces);
    212    }
    213
    214}