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;
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}