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}