001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019
020package org.apache.isis.core.commons.debug;
021
022import java.io.ByteArrayOutputStream;
023import java.io.PrintStream;
024import java.util.StringTokenizer;
025
026public class DebugString implements DebugBuilder {
027
028    private static final int COLUMN_SPACING = 25;
029    private static final int INDENT_WIDTH = 3;
030    private static final String LINE;
031    private static final int MAX_LINE_LENGTH;
032    private static final int MAX_SPACES_LENGTH;
033    private static final String SPACES = "                                                                            ";
034
035    static {
036        LINE = "-------------------------------------------------------------------------------";
037        MAX_LINE_LENGTH = LINE.length();
038        MAX_SPACES_LENGTH = SPACES.length();
039    }
040
041    private int indent = 0;
042    private int section = 1;
043    private final StringBuffer string = new StringBuffer();
044    private boolean newLine = true;
045
046    @Override
047    public void concat(final DebugBuilder debug) {
048        string.append(debug.toString());
049    }
050
051    /**
052     * Append the specified number within a space (number of spaces) specified
053     * by the width. E.g. "15 " where number is 15 and width is 4.
054     */
055    @Override
056    public void append(final int number, final int width) {
057        appendIndent();
058        final int len = string.length();
059        string.append(number);
060        regularizeWidth(width, len);
061    }
062
063    /**
064     * Append the specified object by calling it <code>toString()</code> method.
065     */
066    @Override
067    public void append(final Object object) {
068        if (object instanceof DebuggableWithTitle) {
069            indent();
070            appendTitle(((DebuggableWithTitle) object).debugTitle());
071            ((DebuggableWithTitle) object).debugData(this);
072            unindent();
073        } else {
074            appendIndent();
075            string.append(object);
076        }
077    }
078
079    /**
080     * Append the specified object by calling its <code>toString()</code>
081     * method, placing it within specified space.
082     */
083    @Override
084    public void append(final Object object, final int width) {
085        appendIndent();
086        final int len = string.length();
087        string.append(object);
088        regularizeWidth(width, len);
089    }
090
091    /**
092     * Append the specified number, displayed in hexadecimal notation, with the
093     * specified label, then start a new line.
094     */
095    @Override
096    public void appendAsHexln(final String label, final long value) {
097        appendln(label, "#" + Long.toHexString(value));
098    }
099
100    /**
101     * Append the message and trace of the specified exception.
102     */
103    @Override
104    public void appendException(final Throwable e) {
105        ByteArrayOutputStream baos;
106        final PrintStream s = new PrintStream(baos = new ByteArrayOutputStream());
107        e.printStackTrace(s);
108        appendln(e.getMessage());
109        appendln(new String(baos.toByteArray()));
110        s.close();
111    }
112
113    /**
114     * Start a new line.
115     * 
116     * @see #blankLine()
117     */
118    @Override
119    public void appendln() {
120        string.append('\n');
121        newLine = true;
122    }
123
124    @Override
125    public void appendPreformatted(final String text) {
126        appendln(text);
127    }
128
129    /**
130     * Append the specified text, then start a new line.
131     */
132    @Override
133    public void appendln(final String text) {
134        appendIndent();
135        append(text);
136        appendln();
137        newLine = true;
138    }
139
140    /**
141     * Append the specified value, displayed as true or false, with the
142     * specified label, then start a new line.
143     */
144    @Override
145    public void appendln(final String label, final boolean value) {
146        appendln(label, String.valueOf(value));
147    }
148
149    /**
150     * Append the specified number with the specified label, then start a new
151     * line.
152     */
153    @Override
154    public void appendln(final String label, final double value) {
155        appendln(label, String.valueOf(value));
156    }
157
158    /**
159     * Append the specified number, displayed in hexadecimal notation, with the
160     * specified label, then start a new line.
161     */
162    @Override
163    public void appendln(final String label, final long value) {
164        appendln(label, String.valueOf(value));
165    }
166
167    @Override
168    public void appendPreformatted(final String label, final String text) {
169        StringTokenizer tokenizer = new StringTokenizer(text, "\n\r\f", false);
170        if (tokenizer.hasMoreTokens()) {
171            appendln(label, tokenizer.nextToken());
172        }
173        while (tokenizer.hasMoreTokens()) {
174            string.append(spaces(indent * INDENT_WIDTH + COLUMN_SPACING + 2));
175            string.append(tokenizer.nextToken());
176            string.append('\n');
177        }
178        newLine = true;
179    }
180
181    /**
182     * Append the specified object with the specified label, then start a new
183     * line.
184     */
185    @Override
186    public void appendln(final String label, final Object object) {
187        appendIndent();
188        string.append(label);
189        final int spaces = COLUMN_SPACING - label.length();
190        string.append(": " + spaces(spaces > 0 ? spaces : 0));
191        string.append(object);
192        string.append('\n');
193        newLine = true;
194    }
195
196    /**
197     * Append the elements of the specified array with the specified label. Each
198     * element is appended on its own line, and a new line is added after the
199     * last element.
200     */
201    @Override
202    public void appendln(final String label, final Object[] object) {
203        if (object.length == 0) {
204            appendln(label, "empty array");
205        } else {
206            appendln(label, object[0]);
207            for (int i = 1; i < object.length; i++) {
208                string.append(spaces(COLUMN_SPACING + 2));
209                string.append(object[i]);
210                string.append('\n');
211            }
212            newLine = true;
213        }
214    }
215
216    /**
217     * Append the specified title, then start a new line. A title is shown on
218     * two lines with the text on the first line and dashes on the second.
219     */
220    @Override
221    public void appendTitle(final String title) {
222        appendTitleString(title);
223    }
224
225    private void appendTitleString(final String titleString) {
226        appendln();
227        appendln(titleString);
228        final String underline = LINE.substring(0, Math.min(MAX_LINE_LENGTH, titleString.length()));
229        appendln(underline);
230    }
231
232    @Override
233    public void startSection(final String title) {
234        appendTitleString(section++ + ". " + title);
235        indent();
236    }
237
238    @Override
239    public void endSection() {
240        appendln();
241        unindent();
242    }
243
244    /**
245     * Append a blank line only if there are existing lines and the previous
246     * line is not blank.
247     */
248    @Override
249    public void blankLine() {
250        final int length = string.length();
251        if (length == 0) {
252            return;
253        }
254        final boolean hasLineEnding = string.charAt(length - 1) == '\n';
255        if (!hasLineEnding) {
256            string.append('\n');
257            string.append('\n');
258            newLine = true;
259        } else {
260            final boolean hasDoubleLineEnding = length >= 2 && string.charAt(length - 2) != '\n';
261            if (hasDoubleLineEnding) {
262                string.append('\n');
263                newLine = true;
264            }
265        }
266    }
267
268    /**
269     * Increase indent used when appending.
270     */
271    @Override
272    public void indent() {
273        indent++;
274    }
275
276    private void appendIndent() {
277        if (newLine) {
278            final String spaces = spaces(Math.min(MAX_SPACES_LENGTH, indent * INDENT_WIDTH));
279            string.append(spaces);
280            newLine = false;
281        }
282    }
283
284    private void regularizeWidth(final int width, final int len) {
285        if (width > 0) {
286            final int textWidth = string.length() - len;
287            if (textWidth > width) {
288                string.setLength(len + width - 3);
289                string.append("...");
290            } else {
291                int spaces = width - textWidth;
292                spaces = Math.max(0, spaces);
293                string.append(SPACES.substring(0, spaces));
294            }
295        }
296    }
297
298    private String spaces(final int spaces) {
299        return SPACES.substring(0, spaces);
300    }
301
302    /**
303     * Decrease indent used when appending.
304     */
305    @Override
306    public void unindent() {
307        if (indent > 0) {
308            indent--;
309        }
310    }
311
312    @Override
313    public void close() {
314    }
315
316    /**
317     * Return the <code>String</code> representation of this debug string.
318     */
319    @Override
320    public String toString() {
321        return string.toString();
322    }
323}