Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright 2013, 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      */
     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) {
    191         printWrappedString(stream, string, ConsoleUtil.getConsoleWidth());
    192     }
    193 
    194     public static void printWrappedString(@Nonnull PrintStream stream, @Nonnull String string, int maxWidth) {
    195         for (String str: wrapStringOnBreaks(string, maxWidth)) {
    196             stream.println(str);
    197         }
    198     }
    199 }
    200