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     *
070     * @see java.io.Serializable
071     */
072    private static final long serialVersionUID = 1L;
073
074    /** Indenting of inner lines. */
075    private static final int INDENT = 2;
076
077    /** Current indenting. */
078    private int spaces = 2;
079
080    /**
081     * Constructs a new instance.
082     */
083    public MultilineRecursiveToStringStyle() {
084        resetIndent();
085    }
086
087    @Override
088    protected void appendDetail(final StringBuffer buffer, final String fieldName, final boolean[] array) {
089        spaces += INDENT;
090        resetIndent();
091        super.appendDetail(buffer, fieldName, array);
092        spaces -= INDENT;
093        resetIndent();
094    }
095
096    @Override
097    protected void appendDetail(final StringBuffer buffer, final String fieldName, final byte[] array) {
098        spaces += INDENT;
099        resetIndent();
100        super.appendDetail(buffer, fieldName, array);
101        spaces -= INDENT;
102        resetIndent();
103    }
104
105    @Override
106    protected void appendDetail(final StringBuffer buffer, final String fieldName, final char[] array) {
107        spaces += INDENT;
108        resetIndent();
109        super.appendDetail(buffer, fieldName, array);
110        spaces -= INDENT;
111        resetIndent();
112    }
113
114    @Override
115    protected void appendDetail(final StringBuffer buffer, final String fieldName, final double[] array) {
116        spaces += INDENT;
117        resetIndent();
118        super.appendDetail(buffer, fieldName, array);
119        spaces -= INDENT;
120        resetIndent();
121    }
122
123    @Override
124    protected void appendDetail(final StringBuffer buffer, final String fieldName, final float[] array) {
125        spaces += INDENT;
126        resetIndent();
127        super.appendDetail(buffer, fieldName, array);
128        spaces -= INDENT;
129        resetIndent();
130    }
131
132    @Override
133    protected void appendDetail(final StringBuffer buffer, final String fieldName, final int[] array) {
134        spaces += INDENT;
135        resetIndent();
136        super.appendDetail(buffer, fieldName, array);
137        spaces -= INDENT;
138        resetIndent();
139    }
140
141    @Override
142    protected void appendDetail(final StringBuffer buffer, final String fieldName, final long[] array) {
143        spaces += INDENT;
144        resetIndent();
145        super.appendDetail(buffer, fieldName, array);
146        spaces -= INDENT;
147        resetIndent();
148    }
149
150    @Override
151    public void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) {
152        if (!ClassUtils.isPrimitiveWrapper(value.getClass()) && !String.class.equals(value.getClass())
153                && accept(value.getClass())) {
154            spaces += INDENT;
155            resetIndent();
156            buffer.append(ReflectionToStringBuilder.toString(value, this));
157            spaces -= INDENT;
158            resetIndent();
159        } else {
160            super.appendDetail(buffer, fieldName, value);
161        }
162    }
163
164    @Override
165    protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object[] array) {
166        spaces += INDENT;
167        resetIndent();
168        super.appendDetail(buffer, fieldName, array);
169        spaces -= INDENT;
170        resetIndent();
171    }
172
173    @Override
174    protected void appendDetail(final StringBuffer buffer, final String fieldName, final short[] array) {
175        spaces += INDENT;
176        resetIndent();
177        super.appendDetail(buffer, fieldName, array);
178        spaces -= INDENT;
179        resetIndent();
180    }
181
182    @Override
183    protected void reflectionAppendArrayDetail(final StringBuffer buffer, final String fieldName, final Object array) {
184        spaces += INDENT;
185        resetIndent();
186        super.reflectionAppendArrayDetail(buffer, fieldName, array);
187        spaces -= INDENT;
188        resetIndent();
189    }
190
191    /**
192     * Resets the fields responsible for the line breaks and indenting.
193     * Must be invoked after changing the {@link #spaces} value.
194     */
195    private void resetIndent() {
196        setArrayStart("{" + System.lineSeparator() + spacer(spaces));
197        setArraySeparator("," + System.lineSeparator() + spacer(spaces));
198        setArrayEnd(System.lineSeparator() + spacer(spaces - INDENT) + "}");
199
200        setContentStart("[" + System.lineSeparator() + spacer(spaces));
201        setFieldSeparator("," + System.lineSeparator() + spacer(spaces));
202        setContentEnd(System.lineSeparator() + spacer(spaces - INDENT) + "]");
203    }
204
205    /**
206     * Creates a StringBuilder responsible for the indenting.
207     *
208     * @param spaces how far to indent
209     * @return a StringBuilder with {spaces} leading space characters.
210     */
211    private String spacer(final int spaces) {
212        return StringUtils.repeat(' ', spaces);
213    }
214
215}