/******************************************************************************* * Copyright (c) 2011 Arapiki Solutions Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * "Peter Smith <psmith@arapiki.com>" - initial API and * implementation and/or initial documentation *******************************************************************************/ package com.buildml.utils.print; import java.io.PrintStream; /** * Various utilities for printing things. These are all static methods, so there's no need to * create an instance of the class. * * @author "Peter Smith <psmith@arapiki.com>" * */ public class PrintUtils { /*=====================================================================================* * FIELDS/TYPES *=====================================================================================*/ /** A static string of spaces, providing fast access for printing them. Used by indent(). */ private static String spaces = " "; /*=====================================================================================* * PUBLIC METHODS *=====================================================================================*/ /** * Utility function for printing spaces. This is useful for indented screen output. * @param out The stream to write to (such as System.out). * @param numSpaces The number of spaces to indent by. */ public static void indent(PrintStream out, int numSpaces) { while (true) { /* as much as possible, just write out a substring of our preinitialized spaces string */ if (numSpaces <= spaces.length()){ out.print(spaces.substring(0, numSpaces)); break; } /* but for large indentation, perform multiple writes */ else { out.print(spaces); numSpaces -= spaces.length(); } } } /*-------------------------------------------------------------------------------------*/ /** * Given a multi-line string, display each line of that string after indenting by * the specified number of spaces, and wrapping lines at the specific line width. * Every effort is made to wrap lines at a word boundary, rather than splitting * words across different lines. A line that is wrapped will end with a "\" character. * Note that the wrapWidth includes the number of spaces in indentLevel. That is, * if indentLevel is 20, and wrapWidth is 40, then up to 20 characters will be displayed * per line. * * @param out The stream to write to (such as System.out). * @param string The string to indent, wrap and display. * @param indentLevel The number of characters to indent by. * @param wrapWidth The column number at which to wrap. */ public static void indentAndWrap(PrintStream out, String string, int indentLevel, int wrapWidth) { /* at which index within the string does the current line start? */ int startPos = 0; /* when a line wraps, we'll indent a couple of extra spaces for the next line */ int extraIndentNextTime = 0; /* how long is the total string? */ int stringLen = string.length(); /* how many columns can be used for text (total width - indent) */ int wrapAtColumn = wrapWidth - indentLevel; /* * Record how many spaces each line was already indented by, just * in case we need to wrap a line and indent the next line by * the same amount. */ int prefixSpaces = 0; /* repeat until we've displayed every line of text in the string */ while (startPos < stringLen){ /* * Display the appropriate indentation, possibly with extra indentation * because the previous line was wrapped. */ indent(out, indentLevel + extraIndentNextTime); /* * Figure out how long the next line is - it might not be \n terminated, * so we must allow for the EOL case. */ int endPos = string.indexOf('\n', startPos); if (endPos == -1) { endPos = stringLen; } /* * If this is the beginning of a new line (not just a wrapped line), find * out how many spaces/tabs are at the start of this line. If we end up wrapping * this line across multiple lines, we need to add this amount of indent at * the start of the next line. For example, to wrap: * a b c d e f g * We want to end up with * a b c d \ * e f g * Rather than * a b c d \ * e f g */ if (extraIndentNextTime == 0){ /* not a wrapped line */ prefixSpaces = 0; int pos = startPos; while (pos != endPos) { char ch = string.charAt(pos); if ((ch != ' ') && (ch != '\t')){ break; } prefixSpaces++; pos++; if (ch == '\t') { prefixSpaces += 3; } } } /* would this line need to wrap? That is, is it too long? */ boolean willWrap = false; if ((endPos - startPos) > (wrapAtColumn - extraIndentNextTime)) { /* yes, we need to wrap */ willWrap = true; /* * Find a suitable place to break the line. We first calculate * the maximum length at which we might break the line, although * this might be in the middle of a word. */ endPos = startPos + (wrapAtColumn - extraIndentNextTime); /* * Now check if there's a convenient space (' '), in the second half of this line, * that would be a better place to break things. */ int spacePos = string.lastIndexOf(' ', endPos); if ((spacePos != -1) && (spacePos <= endPos) && (spacePos > ((startPos + endPos) / 2))){ endPos = spacePos + 1; } /* * Because we wrapped this line, we should indent the following line * by a couple of spaces, just for visual appeal. We also make sure * that we indent the next line by the current line's indentation. */ extraIndentNextTime = 2 + prefixSpaces; } /* no wrap this time, so no indentation next time */ else { extraIndentNextTime = 0; } /* display the current line (a substring), with a trailing \ if we wrapped */ out.print(string.substring(startPos, endPos)); if (willWrap) { out.print("\\"); } out.println(); /* * Prepare for printing the next line. If we wrapped, continue from the very next * character. If we didn't wrap, then we need to skip over the \n character */ if (willWrap) { startPos = endPos; } else { startPos = endPos + 1; } } } /*-------------------------------------------------------------------------------------*/ }