001/* 002 * #%L 003 * Netarchivesuite - common 004 * %% 005 * Copyright (C) 2005 - 2018 The Royal Danish Library, 006 * the National Library of France and the Austrian National Library. 007 * %% 008 * This program is free software: you can redistribute it and/or modify 009 * it under the terms of the GNU Lesser General Public License as 010 * published by the Free Software Foundation, either version 2.1 of the 011 * License, or (at your option) any later version. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Lesser Public License for more details. 017 * 018 * You should have received a copy of the GNU General Lesser Public 019 * License along with this program. If not, see 020 * <http://www.gnu.org/licenses/lgpl-2.1.html>. 021 * #L% 022 */ 023package dk.netarkivet.common.utils; 024 025import java.text.DecimalFormat; 026import java.text.SimpleDateFormat; 027import java.util.ArrayList; 028import java.util.Collection; 029import java.util.Date; 030import java.util.List; 031 032import dk.netarkivet.common.exceptions.ArgumentNotValid; 033 034/** 035 * Utilities for working with strings. 036 */ 037public final class StringUtils { 038 039 /** 040 * Utility class, do not initialise. 041 */ 042 private StringUtils() { 043 } 044 045 /** 046 * Replace all occurrences of oldString with newString in a string. 047 * 048 * @param sentence the string, where all occurrences of oldString are to be replaced with newString. 049 * @param oldString the oldString. 050 * @param newString the newString. 051 * @return the resulting string, where all occurrences of oldString are replaced with newString. 052 */ 053 public static String replace(String sentence, String oldString, String newString) { 054 StringBuilder newStr = new StringBuilder(); 055 int found = 0; 056 int lastPointer = 0; 057 do { 058 found = sentence.indexOf(oldString, lastPointer); 059 060 if (found < 0) { 061 newStr.append(sentence.substring(lastPointer, sentence.length())); 062 } else { 063 if (found > lastPointer) { 064 newStr.append(sentence.substring(lastPointer, found)); 065 newStr.append(newString); 066 lastPointer = found + oldString.length(); 067 } 068 } 069 } while (found > -1); 070 return newStr.toString(); 071 } 072 073 /** 074 * Concatenate all objects in a collection with the given separator between each. If the Collection is a List, this 075 * method will generate the conjoined string in list order. If the objects are not Strings, the toString method will 076 * be used to convert them to strings. 077 * 078 * @param sep A string to separate the list items. 079 * @param objects A collection of object to concatenate as a string. 080 * @param <T> The type of objects to conjoin. 081 * @return The concatenated string, or null if the list was null. 082 */ 083 public static <T> String conjoin(String sep, Collection<T> objects) { 084 if (objects == null) { 085 return null; 086 } 087 StringBuilder res = new StringBuilder(); 088 for (T o : objects) { 089 if (res.length() != 0) { 090 res.append(sep); 091 } 092 res.append(o); 093 } 094 return res.toString(); 095 } 096 097 /** 098 * Concatenate the string representation of a maximum number of objects in a collection with a given separator 099 * between them. If the Collection is a List, this method will generate the conjoined string in list order. If the 100 * objects are not Strings, the toString method will be used to convert them to strings. 101 * 102 * @param <T> The type of collection. 103 * @param separator The string to separate the entries in the collection with. This is allowed to be the empty 104 * string. 105 * @param objects The collection to have the string representation of its entries concatenated. 106 * @param max The maximum number of objects in the collection to concatenate. If this number is 0 or below only the 107 * first entry in the collection is returned. 108 * @return The concatenation of the string representation of a limited amount of entries in the collection. 109 * @throws ArgumentNotValid If the separator or the objects are null. 110 */ 111 public static <T> String conjoin(String separator, Collection<T> objects, int max) throws ArgumentNotValid { 112 ArgumentNotValid.checkNotNull(separator, "String separator"); 113 ArgumentNotValid.checkNotNull(objects, "Collection<T> objects"); 114 115 StringBuilder res = new StringBuilder(); 116 int index = 0; 117 // go through all the objects. 118 for (T o : objects) { 119 if (res.length() != 0) { 120 res.append(separator); 121 } 122 res.append(o); 123 124 // check if max is reached. 125 ++index; 126 if (index > max) { 127 break; 128 } 129 } 130 131 return res.toString(); 132 } 133 134 /** 135 * Concatenate all strings in a collection with the given separator between each. 136 * 137 * @param sep A string to separate the list items. 138 * @param strings An array of strings to concatenate. 139 * @return The concatenated string, or null if the list was null. 140 */ 141 public static String conjoin(String sep, String... strings) { 142 if (strings == null) { 143 return null; 144 } 145 StringBuilder res = new StringBuilder(); 146 for (String s : strings) { 147 if (res.length() != 0) { 148 res.append(sep); 149 } 150 res.append(s); 151 } 152 return res.toString(); 153 } 154 155 /** 156 * Concatenate all strings in a collection, with the fixed strings appended and prepended to each. 157 * 158 * @param strings A list of strings to join up. 159 * @param pre A string that will be put in front of each string in the list. 160 * @param post A string that will be put after each string in the list. 161 * @return The joined string, or null if strings is null. 162 */ 163 public static String surjoin(List<String> strings, String pre, String post) { 164 if (strings == null) { 165 return null; 166 } 167 StringBuilder res = new StringBuilder(); 168 for (String s : strings) { 169 res.append(pre); 170 res.append(s); 171 res.append(post); 172 } 173 return res.toString(); 174 } 175 176 /** 177 * Repeat the string n times. 178 * 179 * @param s A string to repeat. 180 * @param n How many times to repeat it. 181 * @return A repeated string. 182 * @throws ArgumentNotValid if a negative amount is specified. 183 */ 184 public static String repeat(String s, int n) { 185 ArgumentNotValid.checkNotNegative(n, "int n"); 186 StringBuilder sb = new StringBuilder(); 187 for (int i = 0; i < n; i++) { 188 sb.append(s); 189 } 190 return sb.toString(); 191 } 192 193 /** 194 * Change all Strings to Integers. 195 * 196 * @param stringArray the given array of Strings to convert. 197 * @return a List of Integers. 198 */ 199 public static List<Integer> parseIntList(String[] stringArray) { 200 List<Integer> resultList = new ArrayList<Integer>(); 201 for (String element : stringArray) { 202 try { 203 resultList.add(Integer.parseInt(element)); 204 } catch (NumberFormatException e) { 205 throw new ArgumentNotValid("Unable to parse '" + element + "' as int"); 206 } 207 } 208 return resultList; 209 } 210 211 /** 212 * Generate a ellipsis of orgString. If orgString is longer than maxLength, then we return a String containing the 213 * first maxLength characters and then append " ..". 214 * 215 * @param orgString the original string. 216 * @param maxLength the maximum length of the string before ellipsing it. 217 * @return an ellipsis of orgString. 218 */ 219 public static String makeEllipsis(String orgString, int maxLength) { 220 ArgumentNotValid.checkNotNull(orgString, "String orgString"); 221 String resultString = orgString; 222 if (orgString.length() > maxLength) { 223 resultString = orgString.substring(0, maxLength - 1) + " .."; 224 } 225 return resultString; 226 } 227 228 /** A day in seconds. */ 229 private static final long DAY = 60 * 60 * 24; 230 /** An hour in seconds. */ 231 private static final long HOUR = 60 * 60; 232 /** A minute in seconds. */ 233 private static final long MINUTE = 60; 234 235 /** Formats a decimal number. */ 236 private static final DecimalFormat DECIMAL = new DecimalFormat("###.##"); 237 238 /** Default date format : yyyy/MM/dd HH:mm:ss */ 239 private static final SimpleDateFormat DEFAULT_DATE = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); 240 241 /** 242 * Formats a duration in seconds as a string of the form "3d 04:12:56". 243 * 244 * @param seconds A duration in seconds 245 * @return a formatted string of the form "3d 04:12:56" 246 */ 247 public static String formatDuration(long seconds) { 248 if (seconds > 0L) { 249 long lRest; 250 251 String strDays = formatDurationLpad(String.valueOf(seconds / DAY)) + "d "; 252 lRest = seconds % DAY; 253 254 String strHours = formatDurationLpad(String.valueOf(lRest / HOUR)) + ":"; 255 lRest %= HOUR; 256 257 String strMinutes = formatDurationLpad(String.valueOf(lRest / MINUTE)) + ":"; 258 lRest %= MINUTE; 259 260 String strSeconds = formatDurationLpad(String.valueOf(lRest)); 261 262 return strDays + strHours + strMinutes + strSeconds; 263 264 } else if (seconds == 0L) { 265 return "0d 00:00:00"; 266 } else { 267 return "-1"; 268 } 269 } 270 271 /** 272 * Leftpad the string with "0", if the string is only one character long. 273 * 274 * @param s The given string 275 * @return Return a string leftpadded with a "0" if the string is only one character long, Otherwise just return the 276 * string. 277 */ 278 private static String formatDurationLpad(final String s) { 279 return (s.length() == 1 ? "0" + s : s); 280 } 281 282 /** 283 * Formats a numeric percentage, as a decimal number with at most 2 digits. 284 * 285 * @param percentage the numeric percentage to format. 286 * @return a formatted percentage string. 287 */ 288 public static String formatPercentage(double percentage) { 289 return formatNumber(percentage) + "%"; 290 } 291 292 /** 293 * Formats a numeric percentage, as a decimal number with at most 2 digits. 294 * 295 * @param percentage the numeric percentage to format. 296 * @return a formatted percentage string. 297 */ 298 public static String formatPercentage(long percentage) { 299 return formatNumber(percentage) + "%"; 300 } 301 302 /** 303 * Formats a number, as a decimal number with at most 2 digits. 304 * 305 * @param number the number to format. 306 * @return a formatted number string. 307 */ 308 public static String formatNumber(double number) { 309 return DECIMAL.format(number); 310 } 311 312 /** 313 * Formats a number, as a decimal number with at most 2 digits. 314 * 315 * @param number the number to format. 316 * @return a formatted number string. 317 */ 318 public static String formatNumber(long number) { 319 return DECIMAL.format(number); 320 } 321 322 /** 323 * Formats the given date (as elapsed milliseconds) using the default format 'yyyy/MM/dd HH:mm:ss'. 324 * 325 * @param millis the date 326 * @return a formatted date string 327 */ 328 public synchronized static String formatDate(long millis) { 329 return DEFAULT_DATE.format(new Date(millis)); 330 } 331 332 /** 333 * Formats the given date (as elapsed milliseconds) using the provided format pattern. 334 * 335 * @param millis the date 336 * @param format the format pattern {@link SimpleDateFormat} 337 * @return a formatted date string 338 */ 339 public static String formatDate(long millis, String format) { 340 return new SimpleDateFormat(format).format(new Date(millis)); 341 } 342 343 /** 344 * Given an input String, this method splits the String with newlines into a multiline String with line-lengths 345 * approximately lineLength. The split is made at the first blank space found at more than lineLength characters 346 * after the previous split. 347 * 348 * @param input the input String. 349 * @param lineLength the desired line length. 350 * @return the split String. 351 * @throws ArgumentNotValid if the input is null or the lineLength is not positive 352 */ 353 public static String splitStringOnWhitespace(String input, int lineLength) { 354 ArgumentNotValid.checkNotNull(input, "input"); 355 ArgumentNotValid.checkPositive(lineLength, "lineLength"); 356 input = input.trim(); 357 String[] inputLines = input.split("\n"); 358 StringBuffer output = new StringBuffer(); 359 for (int i = 0; i < inputLines.length; i++) { 360 int foundIndex = 0; 361 String inputLine = inputLines[i]; 362 while (foundIndex != -1) { 363 foundIndex = inputLine.indexOf(" ", foundIndex + lineLength); 364 // We split after the found blank space so check that this is 365 // meaningful. 366 if (foundIndex != -1 && inputLine.length() > foundIndex + 1) { 367 inputLine = inputLine.substring(0, foundIndex + 1) + "\n" + inputLine.substring(foundIndex + 1); 368 } 369 } 370 output.append(inputLine); 371 output.append("\n"); 372 } 373 return output.toString(); 374 } 375 376 /** 377 * Given a multi-line input string, this method splits the string so that no line has length greater than 378 * maxLineLength. Any input lines less than or equal to this length remain unaffected. 379 * 380 * @param input the input String. 381 * @param maxLineLength the maximum permitted line length. 382 * @return the split multi-line String. 383 * @throws ArgumentNotValid if input is null or maxLineLength is non-positive 384 */ 385 public static String splitStringForce(String input, int maxLineLength) { 386 ArgumentNotValid.checkNotNull(input, "input"); 387 ArgumentNotValid.checkPositive(maxLineLength, "maxLineLength"); 388 input = input.trim(); 389 String[] inputLines = input.split("\n"); 390 StringBuffer output = new StringBuffer(); 391 for (String inputLine : inputLines) { 392 int lastSplittingIndex = 0; 393 int currentLineLength = inputLine.length(); 394 boolean stillSplitting = true; 395 while (stillSplitting) { 396 int nextSplittingIndex = lastSplittingIndex + maxLineLength; 397 if (nextSplittingIndex < currentLineLength - 1) { 398 output.append(inputLine.substring(lastSplittingIndex, nextSplittingIndex)); 399 output.append("\n"); 400 lastSplittingIndex = nextSplittingIndex; 401 } else { 402 output.append(inputLine.substring(lastSplittingIndex)); 403 output.append("\n"); 404 stillSplitting = false; 405 } 406 } 407 } 408 return output.toString().trim(); 409 } 410 411}