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