001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020package org.apache.isis.core.commons.lang; 021 022import java.io.File; 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.List; 026import java.util.StringTokenizer; 027 028import com.google.common.base.Strings; 029 030import org.apache.isis.applib.util.Enums; 031 032public final class StringExtensions { 033 034 static final char SPACE = ' '; 035 036 private StringExtensions() { 037 } 038 039 // //////////////////////////////////////////////////////////// 040 // naturalName, naturalize, simpleName, camel, memberIdFor 041 // //////////////////////////////////////////////////////////// 042 043 /** 044 * Returns a word spaced version of the specified name, so there are spaces 045 * between the words, where each word starts with a capital letter. E.g., 046 * "NextAvailableDate" is returned as "Next Available Date". 047 */ 048 public static String asNaturalName2(final String name) { 049 050 final int length = name.length(); 051 052 if (length <= 1) { 053 return name.toUpperCase();// ensure first character is upper case 054 } 055 056 final StringBuffer naturalName = new StringBuffer(length); 057 058 char previousCharacter; 059 char character = Character.toUpperCase(name.charAt(0));// ensure first 060 // character is 061 // upper case 062 naturalName.append(character); 063 char nextCharacter = name.charAt(1); 064 065 for (int pos = 2; pos < length; pos++) { 066 previousCharacter = character; 067 character = nextCharacter; 068 nextCharacter = name.charAt(pos); 069 070 if (previousCharacter != StringExtensions.SPACE) { 071 if (Character.isUpperCase(character) && !Character.isUpperCase(previousCharacter)) { 072 naturalName.append(StringExtensions.SPACE); 073 } 074 if (Character.isUpperCase(character) && Character.isLowerCase(nextCharacter) && Character.isUpperCase(previousCharacter)) { 075 naturalName.append(StringExtensions.SPACE); 076 } 077 if (Character.isDigit(character) && !Character.isDigit(previousCharacter)) { 078 naturalName.append(StringExtensions.SPACE); 079 } 080 } 081 naturalName.append(character); 082 } 083 naturalName.append(nextCharacter); 084 return naturalName.toString(); 085 } 086 087 public static String asNaturalName(final String extendee) { 088 089 int pos = 0; 090 091 // find first upper case character 092 while ((pos < extendee.length()) && Character.isLowerCase(extendee.charAt(pos))) { 093 pos++; 094 } 095 096 if (pos == extendee.length()) { 097 return "invalid name"; 098 } 099 return naturalized(extendee, pos); 100 } 101 102 public static String asNaturalized(final String extendee) { 103 return naturalized(extendee, 0); 104 } 105 106 private static String naturalized(final String name, final int startingPosition) { 107 if (name.length() <= startingPosition) { 108 throw new IllegalArgumentException("string shorter than starting position provided"); 109 } 110 final StringBuffer s = new StringBuffer(name.length() - startingPosition); 111 for (int j = startingPosition; j < name.length(); j++) { // process 112 // english name 113 // - add spaces 114 if ((j > startingPosition) && isStartOfNewWord(name.charAt(j), name.charAt(j - 1))) { 115 s.append(' '); 116 } 117 if (j == startingPosition) { 118 s.append(Character.toUpperCase(name.charAt(j))); 119 } else { 120 s.append(name.charAt(j)); 121 } 122 } 123 final String str = s.toString(); 124 return str; 125 } 126 127 private static boolean isStartOfNewWord(final char c, final char previousChar) { 128 return Character.isUpperCase(c) || Character.isDigit(c) && !Character.isDigit(previousChar); 129 } 130 131 public static String asSimpleName(final String extendee) { 132 final int lastDot = extendee.lastIndexOf('.'); 133 if (lastDot == -1) { 134 return extendee; 135 } 136 if (lastDot == extendee.length() - 1) { 137 throw new IllegalArgumentException("Name cannot end in '.'"); 138 } 139 return extendee.substring(lastDot + 1); 140 } 141 142 public static String asCamel(final String extendee) { 143 final StringBuffer b = new StringBuffer(extendee.length()); 144 final StringTokenizer t = new StringTokenizer(extendee); 145 b.append(t.nextToken()); 146 while (t.hasMoreTokens()) { 147 final String token = t.nextToken(); 148 b.append(token.substring(0, 1).toUpperCase()); // replace spaces 149 // with 150 // camelCase 151 b.append(token.substring(1)); 152 } 153 return b.toString(); 154 } 155 156 // TODO: combine with camel 157 public static String asCamelLowerFirst(final String extendee) { 158 final StringBuffer b = new StringBuffer(extendee.length()); 159 final StringTokenizer t = new StringTokenizer(extendee); 160 b.append(asLowerFirst(t.nextToken())); 161 while (t.hasMoreTokens()) { 162 final String token = t.nextToken(); 163 b.append(token.substring(0, 1).toUpperCase()); // replace spaces 164 // with camelCase 165 b.append(token.substring(1).toLowerCase()); 166 } 167 return b.toString(); 168 } 169 170 public static String asLowerDashed(String extendee) { 171 return extendee.toLowerCase().replaceAll("\\s+", "-"); 172 } 173 174 public static String asPascal(final String extendee) { 175 return capitalize(asCamel(extendee)); 176 } 177 178 public static String asMemberIdFor(final String extendee) { 179 return asLowerFirst(asCamel(extendee)); 180 } 181 182 // //////////////////////////////////////////////////////////// 183 // capitalize, lowerFirst, firstWord 184 // //////////////////////////////////////////////////////////// 185 186 public static String capitalize(final String extendee) { 187 if (extendee == null || extendee.length() == 0) { 188 return extendee; 189 } 190 if (extendee.length() == 1) { 191 return extendee.toUpperCase(); 192 } 193 return Character.toUpperCase(extendee.charAt(0)) + extendee.substring(1); 194 } 195 196 /** 197 * Simply forces first char to be lower case. 198 */ 199 public static String asLowerFirst(final String extendee) { 200 if (Strings.isNullOrEmpty(extendee)) { 201 return extendee; 202 } 203 if (extendee.length() == 1) { 204 return extendee.toLowerCase(); 205 } 206 return extendee.substring(0, 1).toLowerCase() + extendee.substring(1); 207 } 208 209 public static String asFirstWord(final String extendee) { 210 final String[] split = extendee.split(" "); 211 return split[0]; 212 } 213 214 215 // //////////////////////////////////////////////////////////// 216 // in, combinePaths, splitOnCommas 217 // //////////////////////////////////////////////////////////// 218 219 public static boolean in(final String extendee, final String[] strings) { 220 for (final String strCandidate : strings) { 221 if (strCandidate.equals(extendee)) { 222 return true; 223 } 224 } 225 return false; 226 } 227 228 public static String combinePaths(final String extendee, final String... furtherPaths) { 229 final StringBuilder pathBuf = new StringBuilder(extendee); 230 for (final String furtherPath : furtherPaths) { 231 if (pathBuf.charAt(pathBuf.length() - 1) != File.separatorChar) { 232 pathBuf.append(File.separatorChar); 233 } 234 pathBuf.append(furtherPath); 235 } 236 return pathBuf.toString(); 237 } 238 239 public static List<String> splitOnCommas(final String commaSeparatedExtendee) { 240 if (commaSeparatedExtendee == null) { 241 return null; 242 } 243 final String removeLeadingWhiteSpace = removeLeadingWhiteSpace(commaSeparatedExtendee); 244 // special handling 245 if (removeLeadingWhiteSpace.length() == 0) { 246 return Collections.emptyList(); 247 } 248 final String[] splitAsArray = removeLeadingWhiteSpace.split("\\W*,\\W*"); 249 return Arrays.asList(splitAsArray); 250 } 251 252 253 private static final char CARRIAGE_RETURN = '\n'; 254 private static final char LINE_FEED = '\r'; 255 256 /** 257 * Converts any <tt>\n</tt> to <tt>line.separator</tt> 258 * 259 * @param extendee 260 * @return 261 */ 262 public static String lineSeparated(final String extendee) { 263 final StringBuilder buf = new StringBuilder(); 264 final String lineSeparator = System.getProperty("line.separator"); 265 boolean lastWasLineFeed = false; 266 for (final char c : extendee.toCharArray()) { 267 final boolean isLineFeed = c == LINE_FEED; 268 final boolean isCarriageReturn = c == CARRIAGE_RETURN; 269 if (isCarriageReturn) { 270 buf.append(lineSeparator); 271 lastWasLineFeed = false; 272 } else { 273 if (lastWasLineFeed) { 274 buf.append(LINE_FEED); 275 } 276 if (isLineFeed) { 277 lastWasLineFeed = true; 278 } else { 279 buf.append(c); 280 lastWasLineFeed = false; 281 } 282 } 283 } 284 if (lastWasLineFeed) { 285 buf.append(LINE_FEED); 286 } 287 return buf.toString(); 288 } 289 290 // //////////////////////////////////////////////////////////// 291 // removeTabs, removeLeadingWhiteSpace, stripLeadingSlash, stripNewLines, 292 // normalize 293 // //////////////////////////////////////////////////////////// 294 295 public static String removeTabs(final String extendee) { 296 // quick return - jvm java should always return here 297 if (extendee.indexOf('\t') == -1) { 298 return extendee; 299 } 300 final StringBuffer buf = new StringBuffer(); 301 for (int i = 0; i < extendee.length(); i++) { 302 // a bit clunky to stay with j# api 303 if (extendee.charAt(i) != '\t') { 304 buf.append(extendee.charAt(i)); 305 } 306 } 307 return buf.toString(); 308 } 309 310 public static String removeLeadingWhiteSpace(final String extendee) { 311 if (extendee == null) { 312 return null; 313 } 314 return extendee.replaceAll("^\\W*", ""); 315 } 316 317 public static String stripNewLines(final String extendee) { 318 return extendee.replaceAll("[\r\n]", ""); 319 } 320 321 public static String stripLeadingSlash(final String extendee) { 322 if (!extendee.startsWith("/")) { 323 return extendee; 324 } 325 if (extendee.length() < 2) { 326 return ""; 327 } 328 return extendee.substring(1); 329 } 330 331 /** 332 * Condenses any whitespace to a single character 333 * 334 * @param extendee 335 * @return 336 */ 337 public static String normalized(final String extendee) { 338 if (extendee == null) { 339 return null; 340 } 341 return extendee.replaceAll("\\s+", " "); 342 } 343 344 public static String removePrefix(final String extendee, final String prefix) { 345 return extendee.startsWith(prefix) 346 ? extendee.substring(prefix.length()) 347 : extendee; 348 } 349 350 public static String enumTitle(String enumName) { 351 return Enums.getFriendlyNameOf(enumName); 352 } 353 354 public static String enumDeTitle(String enumFriendlyName) { 355 return Enums.getEnumNameFromFriendly(enumFriendlyName); 356 } 357 358 /* 359 * eg converts <tt>HiddenFacetForMemberAnnotation</tt> to <tt>HFFMA</tt>. 360 */ 361 public static String toAbbreviation(final String extendee) { 362 final StringBuilder buf = new StringBuilder(); 363 for(char c: extendee.toCharArray()) { 364 if(Character.isUpperCase(c)) { 365 buf.append(c); 366 } 367 } 368 return buf.toString(); 369 } 370 371 372 // ////////////////////////////////////// 373 // copied in from Apache Commons 374 // ////////////////////////////////////// 375 376 377 public static final String EMPTY = ""; 378 /** 379 * <p>The maximum size to which the padding constant(s) can expand.</p> 380 */ 381 private static final int PAD_LIMIT = 8192; 382 383 /** 384 * <p>Repeat a String <code>repeat</code> times to form a 385 * new String.</p> 386 * 387 * <pre> 388 * StringUtils.repeat(null, 2) = null 389 * StringUtils.repeat("", 0) = "" 390 * StringUtils.repeat("", 2) = "" 391 * StringUtils.repeat("a", 3) = "aaa" 392 * StringUtils.repeat("ab", 2) = "abab" 393 * StringUtils.repeat("a", -2) = "" 394 * </pre> 395 * 396 * @param extendee the String to repeat, may be null 397 * @param repeat number of times to repeat str, negative treated as zero 398 * @return a new String consisting of the original String repeated, 399 * <code>null</code> if null String input 400 */ 401 public static String repeat(final String extendee, int repeat) { 402 // Performance tuned for 2.0 (JDK1.4) 403 404 if (extendee == null) { 405 return null; 406 } 407 if (repeat <= 0) { 408 return EMPTY; 409 } 410 int inputLength = extendee.length(); 411 if (repeat == 1 || inputLength == 0) { 412 return extendee; 413 } 414 if (inputLength == 1 && repeat <= PAD_LIMIT) { 415 return padding(repeat, extendee.charAt(0)); 416 } 417 418 int outputLength = inputLength * repeat; 419 switch (inputLength) { 420 case 1 : 421 char ch = extendee.charAt(0); 422 char[] output1 = new char[outputLength]; 423 for (int i = repeat - 1; i >= 0; i--) { 424 output1[i] = ch; 425 } 426 return new String(output1); 427 case 2 : 428 char ch0 = extendee.charAt(0); 429 char ch1 = extendee.charAt(1); 430 char[] output2 = new char[outputLength]; 431 for (int i = repeat * 2 - 2; i >= 0; i--, i--) { 432 output2[i] = ch0; 433 output2[i + 1] = ch1; 434 } 435 return new String(output2); 436 default : 437 StringBuilder buf = new StringBuilder(outputLength); 438 for (int i = 0; i < repeat; i++) { 439 buf.append(extendee); 440 } 441 return buf.toString(); 442 } 443 } 444 445 /** 446 * <p>Returns padding using the specified delimiter repeated 447 * to a given length.</p> 448 * 449 * <pre> 450 * StringUtils.padding(0, 'e') = "" 451 * StringUtils.padding(3, 'e') = "eee" 452 * StringUtils.padding(-2, 'e') = IndexOutOfBoundsException 453 * </pre> 454 * 455 * <p>Note: this method doesn't not support padding with 456 * <a href="http://www.unicode.org/glossary/#supplementary_character">Unicode Supplementary Characters</a> 457 * as they require a pair of <code>char</code>s to be represented. 458 * If you are needing to support full I18N of your applications 459 * consider using {@link #repeat(String, int)} instead. 460 * </p> 461 * 462 * @param repeat number of times to repeat delim 463 * @param padChar character to repeat 464 * @return String with repeated character 465 * @throws IndexOutOfBoundsException if <code>repeat < 0</code> 466 * @see #repeat(String, int) 467 */ 468 private static String padding(int repeat, char padChar) throws IndexOutOfBoundsException { 469 if (repeat < 0) { 470 throw new IndexOutOfBoundsException("Cannot pad a negative amount: " + repeat); 471 } 472 final char[] buf = new char[repeat]; 473 for (int i = 0; i < buf.length; i++) { 474 buf[i] = padChar; 475 } 476 return new String(buf); 477 } 478 479 public static boolean startsWith(final String extendee, final String prefix) { 480 final int length = prefix.length(); 481 if (length >= extendee.length()) { 482 return false; 483 } else { 484 final char startingCharacter = extendee.charAt(length); 485 return extendee.startsWith(prefix) && Character.isUpperCase(startingCharacter); 486 } 487 } 488 489 public static String combinePath(final String extendee, final String suffix) { 490 if (Strings.isNullOrEmpty(extendee) && Strings.isNullOrEmpty(suffix)) { 491 return ""; 492 } 493 if (Strings.isNullOrEmpty(extendee)) { 494 return suffix; 495 } 496 if (Strings.isNullOrEmpty(suffix)) { 497 return extendee; 498 } 499 if (extendee.endsWith("/") || suffix.startsWith("/")) { 500 return extendee + suffix; 501 } 502 return extendee + "/" + suffix; 503 } 504 505 /** 506 * Returns the name of a Java entity without any prefix. A prefix is defined 507 * as the first set of lowercase letters and the name is characters from, 508 * and including, the first upper case letter. If no upper case letter is 509 * found then an empty string is returned. 510 * 511 * <p> 512 * Calling this method with the following Java names will produce these 513 * results: 514 * 515 * <pre> 516 * getCarRegistration -> CarRegistration 517 * CityMayor -> CityMayor 518 * isReady -> Ready 519 * </pre> 520 * 521 */ 522 public static String asJavaBaseName(final String javaName) { 523 int pos = 0; 524 525 // find first upper case character 526 final int len = javaName.length(); 527 528 while ((pos < len) && (javaName.charAt(pos) != '_') && Character.isLowerCase(javaName.charAt(pos))) { 529 pos++; 530 } 531 532 if (pos >= len) { 533 return ""; 534 } 535 536 if (javaName.charAt(pos) == '_') { 537 pos++; 538 } 539 540 if (pos >= len) { 541 return ""; 542 } 543 544 final String baseName = javaName.substring(pos); 545 final char firstChar = baseName.charAt(0); 546 547 if (Character.isLowerCase(firstChar)) { 548 return Character.toUpperCase(firstChar) + baseName.substring(1); 549 } else { 550 return baseName; 551 } 552 } 553 554 public static String asJavaBaseNameStripAccessorPrefixIfRequired(final String javaNameExtendee) { 555 if (javaNameExtendee.startsWith("is") || javaNameExtendee.startsWith("get")) { 556 return asJavaBaseName(javaNameExtendee); 557 } else { 558 return StringExtensions.asCapitalizedName(javaNameExtendee); 559 } 560 } 561 562 public static String asCapitalizedName(final String extendee) { 563 return Character.toUpperCase(extendee.charAt(0)) + extendee.substring(1); 564 } 565 566 567 public static String asPluralName(final String extendee) { 568 String pluralName; 569 if (extendee.endsWith("y")) { 570 pluralName = extendee.substring(0, extendee.length() - 1) + "ies"; 571 } else if (extendee.endsWith("s") || extendee.endsWith("x")) { 572 pluralName = extendee + "es"; 573 } else { 574 pluralName = extendee + 's'; 575 } 576 return pluralName; 577 } 578 579 public static String toCamelCase(final String extendee) { 580 final String nameLower = extendee.toLowerCase(); 581 final StringBuilder buf = new StringBuilder(); 582 boolean capitalizeNext = false; 583 for (int i = 0; i < nameLower.length(); i++) { 584 final char ch = nameLower.charAt(i); 585 if (ch == '_') { 586 capitalizeNext = true; 587 } else { 588 if (capitalizeNext) { 589 buf.append(Character.toUpperCase(ch)); 590 } else { 591 buf.append(ch); 592 } 593 capitalizeNext = false; 594 } 595 } 596 return buf.toString(); 597 } 598 599 600 601 602}