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