// Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package net.sourceforge.eclipsejetty.starter.console.util; import net.sourceforge.eclipsejetty.starter.util.Utils; /** * Provides word wrap functionality. * * @author Manfred Hantschl * @author Thomas Moser */ public final class WordWrap { private static final String DEFAULT_UNCUTABLE = "\"\'!$%()[]{}?.,:;"; private String cutable = Utils.EMPTY; private String uncutable = DEFAULT_UNCUTABLE; private int tabWidth = 8; private String lineSeparator = System.getProperty("line.separator"); /** * Creates a word wrap instance */ public WordWrap() { super(); } /** * Defines the character to indicate a cut position (after the character) * * @param c the character * @return the instance */ public WordWrap cutable(final char c) { if (cutable.indexOf(c) < 0) { cutable += 1; } return this; } /** * Defines the character to indicate a position with not cut-ability (after the character). Default is: all letters, * all digits and "'!$%()[]{}?.,:; * * @param c the character * @return the instance */ public WordWrap uncutable(final char c) { if (uncutable.indexOf(c) < 0) { uncutable += c; } return this; } private boolean isCutable(final char ch) { return (cutable.indexOf(ch) >= 0) || (!((Character.isLetter(ch)) || (Character.isDigit(ch)) || (uncutable.indexOf(ch) >= 0))); } /** * Defines the width of one tab, default is 8 * * @param withTabWidth the width of one tab * @return the instance */ public WordWrap withTabWidth(final int withTabWidth) { tabWidth = withTabWidth; return this; } /** * Sets the line separator, default is defined by OS * * @param withLineSeparator the line separator * @return the instance */ public WordWrap withLineSeparator(final String withLineSeparator) { lineSeparator = withLineSeparator; return this; } /** * Performs a wordwrap on the text. The operation is thread-safe. You can use one {@link WordWrap} instance with the * same configuration for multiple threads. * * @param text the text * @param length the length of each line * @return the warped text */ //CHECKSTYLE:OFF fixing complexity would mean rewrite public String perform(final String text, final int length) { if (text == null) { return null; } StringBuilder result = new StringBuilder(); int lineBegin = 0; int position = 0; int lineLength = 0; int possibleCutPosition = -1; boolean isCutable = false; boolean wasCutable = false; boolean readingIndent = true; String indent = Utils.EMPTY; while (position < text.length()) { char ch = text.charAt(position); if (ch == '\n') { // the current char is a line feed. Reset the line. result.append(text.substring(lineBegin, position)).append(lineSeparator); position += 1; lineBegin = position; lineLength = 0; possibleCutPosition = -1; isCutable = false; wasCutable = false; readingIndent = true; indent = Utils.EMPTY; } else if (ch == '\r') { // the current char is a carriage return. Reset the line. result.append(text.substring(lineBegin, position)).append(lineSeparator); position += 1; lineBegin = position; lineLength = 0; possibleCutPosition = -1; isCutable = false; wasCutable = false; readingIndent = true; indent = Utils.EMPTY; if (((position + 1) < text.length()) && (text.charAt(position + 1) == '\n')) { // we are in dos here, skip the carriage return. position += 1; } } else { wasCutable = isCutable; if (ch == '\t') { // the current char is a tab. This is a special cut position. Additional advance // the length possibleCutPosition = position; lineLength += tabWidth; isCutable = true; if (readingIndent) { indent += ch; } } else { lineLength += 1; if (ch == ' ') { // the current char is a space. This is a special cut position. possibleCutPosition = position; isCutable = true; if (readingIndent) { indent += ch; } } else { isCutable = isCutable(ch); readingIndent = false; if ((wasCutable) && (!isCutable)) { // found cut position, because the last char was cutable, but the // current isn't possibleCutPosition = position; } } } if (lineLength > length) { // we have to cut now if (possibleCutPosition > -1) { // there was a position where i could cut result.append(text.substring(lineBegin, possibleCutPosition)).append(lineSeparator); // append the indent for the next line, TODO this is possibly wrong here result.append(indent); position = possibleCutPosition; while ((position < text.length()) && (text.charAt(position) == ' ')) { // skip the white-spaces position += 1; } lineBegin = position; lineLength = 0; possibleCutPosition = -1; isCutable = false; wasCutable = false; } else { // there was no cut position, cut right now result.append(text.substring(lineBegin, position)).append(lineSeparator); // append the indent for the next line, TODO this is possibly wrong here result.append(indent); while ((position < text.length()) && (text.charAt(position) == ' ')) { // skip the white-spaces position += 1; } lineBegin = position; lineLength = 0; possibleCutPosition = -1; isCutable = false; wasCutable = false; } } else { position += 1; } } } if (lineBegin < position) { result.append(text.substring(lineBegin)); } return result.toString(); } //CHECKSTYLE:ON }