1 /* 2 * Copyright 2018, Google Inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 package org.jf.util; 33 34 import javax.annotation.Nonnull; 35 import javax.annotation.Nullable; 36 import java.io.PrintStream; 37 import java.text.BreakIterator; 38 import java.util.Iterator; 39 40 public class StringWrapper { 41 /** 42 * Splits the given string into lines of maximum width maxWidth. The splitting is done using the current locale's 43 * rules for splitting lines. 44 * 45 * @param string The string to split 46 * @param maxWidth The maximum length of any line 47 * @return An iterable of Strings containing the wrapped lines 48 */ wrapStringOnBreaks(@onnull final String string, final int maxWidth)49 public static Iterable<String> wrapStringOnBreaks(@Nonnull final String string, final int maxWidth) { 50 // TODO: should we strip any trailing newlines? 51 final BreakIterator breakIterator = BreakIterator.getLineInstance(); 52 breakIterator.setText(string); 53 54 return new Iterable<String>() { 55 @Override 56 public Iterator<String> iterator() { 57 return new Iterator<String>() { 58 private int currentLineStart = 0; 59 private boolean nextLineSet = false; 60 private String nextLine; 61 62 @Override 63 public boolean hasNext() { 64 if (!nextLineSet) { 65 calculateNext(); 66 } 67 return nextLine != null; 68 } 69 70 private void calculateNext() { 71 int lineEnd = currentLineStart; 72 while (true) { 73 lineEnd = breakIterator.following(lineEnd); 74 if (lineEnd == BreakIterator.DONE) { 75 lineEnd = breakIterator.last(); 76 if (lineEnd <= currentLineStart) { 77 nextLine = null; 78 nextLineSet = true; 79 return; 80 } 81 break; 82 } 83 84 if (lineEnd - currentLineStart > maxWidth) { 85 lineEnd = breakIterator.preceding(lineEnd); 86 if (lineEnd <= currentLineStart) { 87 lineEnd = currentLineStart + maxWidth; 88 } 89 break; 90 } 91 92 if (string.charAt(lineEnd-1) == '\n') { 93 nextLine = string.substring(currentLineStart, lineEnd-1); 94 nextLineSet = true; 95 currentLineStart = lineEnd; 96 return; 97 } 98 } 99 nextLine = string.substring(currentLineStart, lineEnd); 100 nextLineSet = true; 101 currentLineStart = lineEnd; 102 } 103 104 @Override 105 public String next() { 106 String ret = nextLine; 107 nextLine = null; 108 nextLineSet = false; 109 return ret; 110 } 111 112 @Override 113 public void remove() { 114 throw new UnsupportedOperationException(); 115 } 116 }; 117 } 118 }; 119 } 120 121 /** 122 * Splits the given string into lines using on any embedded newlines, and wrapping the text as needed to conform to 123 * the given maximum line width. 124 * 125 * This uses and assumes unix-style newlines 126 * 127 * @param str The string to split 128 * @param maxWidth The maximum length of any line 129 * @param output If given, try to use this array as the return value. If there are more values than will fit 130 * into the array, a new array will be allocated and returned, while the given array will be filled 131 * with as many lines as would fit. 132 * @return The split lines from the original, as an array of Strings. The returned array may be larger than the 133 * number of lines. If this is the case, the end of the split lines will be denoted by a null entry in the 134 * array. If there is no null entry, then the size of the array exactly matches the number of lines. 135 * The returned lines will not contain an ending newline 136 */ 137 public static String[] wrapString(@Nonnull String str, int maxWidth, @Nullable String[] output) { 138 if (output == null) { 139 output = new String[(int)((str.length() / maxWidth) * 1.5d + 1)]; 140 } 141 142 int lineStart = 0; 143 int arrayIndex = 0; 144 int i; 145 for (i=0; i<str.length(); i++) { 146 char c = str.charAt(i); 147 148 if (c == '\n') { 149 output = addString(output, str.substring(lineStart, i), arrayIndex++); 150 lineStart = i+1; 151 } else if (i - lineStart == maxWidth) { 152 output = addString(output, str.substring(lineStart, i), arrayIndex++); 153 lineStart = i; 154 } 155 } 156 if (lineStart != i || i == 0) { 157 output = addString(output, str.substring(lineStart), arrayIndex++, output.length+1); 158 } 159 160 if (arrayIndex < output.length) { 161 output[arrayIndex] = null; 162 } 163 return output; 164 } 165 166 private static String[] addString(@Nonnull String[] arr, String str, int index) { 167 if (index >= arr.length) { 168 arr = enlargeArray(arr, (int)(Math.ceil((arr.length + 1) * 1.5))); 169 } 170 171 arr[index] = str; 172 return arr; 173 } 174 175 private static String[] addString(@Nonnull String[] arr, String str, int index, int newLength) { 176 if (index >= arr.length) { 177 arr = enlargeArray(arr, newLength); 178 } 179 180 arr[index] = str; 181 return arr; 182 } 183 184 private static String[] enlargeArray(String[] arr, int newLength) { 185 String[] newArr = new String[newLength]; 186 System.arraycopy(arr, 0, newArr, 0, arr.length); 187 return newArr; 188 } 189 190 public static void printWrappedString(@Nonnull PrintStream stream, @Nonnull String string, int maxWidth) { 191 for (String str: wrapStringOnBreaks(string, maxWidth)) { 192 stream.println(str); 193 } 194 } 195 } 196