/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.isis.core.commons.lang;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.StringTokenizer;
import com.google.common.base.Strings;
import org.apache.isis.applib.util.Enums;
public final class StringExtensions {
static final char SPACE = ' ';
private StringExtensions() {
}
// ////////////////////////////////////////////////////////////
// naturalName, naturalize, simpleName, camel, memberIdFor
// ////////////////////////////////////////////////////////////
/**
* Returns a word spaced version of the specified name, so there are spaces
* between the words, where each word starts with a capital letter. E.g.,
* "NextAvailableDate" is returned as "Next Available Date".
*/
public static String asNaturalName2(String name) {
// handle any nested class names, eg 'Foo$Bar'
final int idx = name.lastIndexOf("$");
if(idx != -1) {
name = name.substring(idx+1);
}
final int length = name.length();
if (length <= 1) {
return name.toUpperCase();// ensure first character is upper case
}
final StringBuilder naturalName = new StringBuilder(length);
char previousCharacter;
char character = Character.toUpperCase(name.charAt(0));// ensure first
// character is
// upper case
naturalName.append(character);
char nextCharacter = name.charAt(1);
for (int pos = 2; pos < length; pos++) {
previousCharacter = character;
character = nextCharacter;
nextCharacter = name.charAt(pos);
if (previousCharacter != StringExtensions.SPACE) {
if (Character.isUpperCase(character) && !Character.isUpperCase(previousCharacter)) {
naturalName.append(StringExtensions.SPACE);
}
if (Character.isUpperCase(character) && Character.isLowerCase(nextCharacter) && Character.isUpperCase(previousCharacter)) {
naturalName.append(StringExtensions.SPACE);
}
if (Character.isDigit(character) && !Character.isDigit(previousCharacter)) {
naturalName.append(StringExtensions.SPACE);
}
}
naturalName.append(character);
}
naturalName.append(nextCharacter);
return naturalName.toString();
}
public static String asNaturalName(final String extendee) {
int pos = 0;
// find first upper case character
while ((pos < extendee.length()) && Character.isLowerCase(extendee.charAt(pos))) {
pos++;
}
if (pos == extendee.length()) {
return "invalid name";
}
return naturalized(extendee, pos);
}
public static String asNaturalized(final String extendee) {
return naturalized(extendee, 0);
}
private static String naturalized(final String name, final int startingPosition) {
if (name.length() <= startingPosition) {
throw new IllegalArgumentException("string shorter than starting position provided");
}
final StringBuffer s = new StringBuffer(name.length() - startingPosition);
for (int j = startingPosition; j < name.length(); j++) { // process
// english name
// - add spaces
if ((j > startingPosition) && isStartOfNewWord(name.charAt(j), name.charAt(j - 1))) {
s.append(' ');
}
if (j == startingPosition) {
s.append(Character.toUpperCase(name.charAt(j)));
} else {
s.append(name.charAt(j));
}
}
final String str = s.toString();
return str;
}
private static boolean isStartOfNewWord(final char c, final char previousChar) {
return Character.isUpperCase(c) || Character.isDigit(c) && !Character.isDigit(previousChar);
}
public static String asSimpleName(final String extendee) {
final int lastDot = extendee.lastIndexOf('.');
if (lastDot == -1) {
return extendee;
}
if (lastDot == extendee.length() - 1) {
throw new IllegalArgumentException("Name cannot end in '.'");
}
return extendee.substring(lastDot + 1);
}
public static String asCamel(final String extendee) {
final StringBuffer b = new StringBuffer(extendee.length());
final StringTokenizer t = new StringTokenizer(extendee);
b.append(t.nextToken());
while (t.hasMoreTokens()) {
final String token = t.nextToken();
b.append(token.substring(0, 1).toUpperCase()); // replace spaces
// with
// camelCase
b.append(token.substring(1));
}
return b.toString();
}
// TODO: combine with camel
public static String asCamelLowerFirst(final String extendee) {
final StringBuffer b = new StringBuffer(extendee.length());
final StringTokenizer t = new StringTokenizer(extendee);
b.append(asLowerFirst(t.nextToken()));
while (t.hasMoreTokens()) {
final String token = t.nextToken();
b.append(token.substring(0, 1).toUpperCase()); // replace spaces
// with camelCase
b.append(token.substring(1).toLowerCase());
}
return b.toString();
}
public static String asLowerDashed(String extendee) {
return extendee.toLowerCase().replaceAll("\\s+", "-");
}
public static String asPascal(final String extendee) {
return capitalize(asCamel(extendee));
}
public static String asMemberIdFor(final String extendee) {
return asLowerFirst(asCamel(extendee));
}
// ////////////////////////////////////////////////////////////
// capitalize, lowerFirst, firstWord
// ////////////////////////////////////////////////////////////
public static String capitalize(final String extendee) {
if (extendee == null || extendee.length() == 0) {
return extendee;
}
if (extendee.length() == 1) {
return extendee.toUpperCase();
}
return Character.toUpperCase(extendee.charAt(0)) + extendee.substring(1);
}
/**
* Simply forces first char to be lower case.
*/
public static String asLowerFirst(final String extendee) {
if (Strings.isNullOrEmpty(extendee)) {
return extendee;
}
if (extendee.length() == 1) {
return extendee.toLowerCase();
}
return extendee.substring(0, 1).toLowerCase() + extendee.substring(1);
}
public static String asFirstWord(final String extendee) {
final String[] split = extendee.split(" ");
return split[0];
}
// ////////////////////////////////////////////////////////////
// in, combinePaths, splitOnCommas
// ////////////////////////////////////////////////////////////
public static boolean in(final String extendee, final String[] strings) {
for (final String strCandidate : strings) {
if (strCandidate.equals(extendee)) {
return true;
}
}
return false;
}
public static String combinePaths(final String extendee, final String... furtherPaths) {
final StringBuilder pathBuf = new StringBuilder(extendee);
for (final String furtherPath : furtherPaths) {
if (pathBuf.charAt(pathBuf.length() - 1) != File.separatorChar) {
pathBuf.append(File.separatorChar);
}
pathBuf.append(furtherPath);
}
return pathBuf.toString();
}
public static List<String> splitOnCommas(final String commaSeparatedExtendee) {
if (commaSeparatedExtendee == null) {
return null;
}
final String removeLeadingWhiteSpace = removeLeadingWhiteSpace(commaSeparatedExtendee);
// special handling
if (removeLeadingWhiteSpace.length() == 0) {
return Collections.emptyList();
}
final String[] splitAsArray = removeLeadingWhiteSpace.split("\\W*,\\W*");
return Arrays.asList(splitAsArray);
}
private static final char CARRIAGE_RETURN = '\n';
private static final char LINE_FEED = '\r';
/**
* Converts any <tt>\n</tt> to <tt>line.separator</tt>
*
* @param extendee
* @return
*/
public static String lineSeparated(final String extendee) {
final StringBuilder buf = new StringBuilder();
final String lineSeparator = System.getProperty("line.separator");
boolean lastWasLineFeed = false;
for (final char c : extendee.toCharArray()) {
final boolean isLineFeed = c == LINE_FEED;
final boolean isCarriageReturn = c == CARRIAGE_RETURN;
if (isCarriageReturn) {
buf.append(lineSeparator);
lastWasLineFeed = false;
} else {
if (lastWasLineFeed) {
buf.append(LINE_FEED);
}
if (isLineFeed) {
lastWasLineFeed = true;
} else {
buf.append(c);
lastWasLineFeed = false;
}
}
}
if (lastWasLineFeed) {
buf.append(LINE_FEED);
}
return buf.toString();
}
// ////////////////////////////////////////////////////////////
// removeTabs, removeLeadingWhiteSpace, stripLeadingSlash, stripNewLines,
// normalize
// ////////////////////////////////////////////////////////////
public static String removeTabs(final String extendee) {
// quick return - jvm java should always return here
if (extendee.indexOf('\t') == -1) {
return extendee;
}
final StringBuffer buf = new StringBuffer();
for (int i = 0; i < extendee.length(); i++) {
// a bit clunky to stay with j# api
if (extendee.charAt(i) != '\t') {
buf.append(extendee.charAt(i));
}
}
return buf.toString();
}
public static String removeLeadingWhiteSpace(final String extendee) {
if (extendee == null) {
return null;
}
return extendee.replaceAll("^\\W*", "");
}
public static String stripNewLines(final String extendee) {
return extendee.replaceAll("[\r\n]", "");
}
public static String stripLeadingSlash(final String extendee) {
if (!extendee.startsWith("/")) {
return extendee;
}
if (extendee.length() < 2) {
return "";
}
return extendee.substring(1);
}
/**
* Condenses any whitespace to a single character
*
* @param extendee
* @return
*/
public static String normalized(final String extendee) {
if (extendee == null) {
return null;
}
return extendee.replaceAll("\\s+", " ");
}
public static String removePrefix(final String extendee, final String prefix) {
return extendee.startsWith(prefix)
? extendee.substring(prefix.length())
: extendee;
}
public static String enumTitle(String enumName) {
return Enums.getFriendlyNameOf(enumName);
}
public static String enumDeTitle(String enumFriendlyName) {
return Enums.getEnumNameFromFriendly(enumFriendlyName);
}
/*
* eg converts <tt>HiddenFacetForMemberAnnotation</tt> to <tt>HFFMA</tt>.
*/
public static String toAbbreviation(final String extendee) {
final StringBuilder buf = new StringBuilder();
for(char c: extendee.toCharArray()) {
if(Character.isUpperCase(c)) {
buf.append(c);
}
}
return buf.toString();
}
// //////////////////////////////////////
// copied in from Apache Commons
// //////////////////////////////////////
public static final String EMPTY = "";
/**
* <p>The maximum size to which the padding constant(s) can expand.</p>
*/
private static final int PAD_LIMIT = 8192;
/**
* <p>Repeat a String <code>repeat</code> times to form a
* new String.</p>
*
* <pre>
* StringUtils.repeat(null, 2) = null
* StringUtils.repeat("", 0) = ""
* StringUtils.repeat("", 2) = ""
* StringUtils.repeat("a", 3) = "aaa"
* StringUtils.repeat("ab", 2) = "abab"
* StringUtils.repeat("a", -2) = ""
* </pre>
*
* @param extendee the String to repeat, may be null
* @param repeat number of times to repeat str, negative treated as zero
* @return a new String consisting of the original String repeated,
* <code>null</code> if null String input
*/
public static String repeat(final String extendee, int repeat) {
// Performance tuned for 2.0 (JDK1.4)
if (extendee == null) {
return null;
}
if (repeat <= 0) {
return EMPTY;
}
int inputLength = extendee.length();
if (repeat == 1 || inputLength == 0) {
return extendee;
}
if (inputLength == 1 && repeat <= PAD_LIMIT) {
return padding(repeat, extendee.charAt(0));
}
int outputLength = inputLength * repeat;
switch (inputLength) {
case 1 :
char ch = extendee.charAt(0);
char[] output1 = new char[outputLength];
for (int i = repeat - 1; i >= 0; i--) {
output1[i] = ch;
}
return new String(output1);
case 2 :
char ch0 = extendee.charAt(0);
char ch1 = extendee.charAt(1);
char[] output2 = new char[outputLength];
for (int i = repeat * 2 - 2; i >= 0; i--, i--) {
output2[i] = ch0;
output2[i + 1] = ch1;
}
return new String(output2);
default :
StringBuilder buf = new StringBuilder(outputLength);
for (int i = 0; i < repeat; i++) {
buf.append(extendee);
}
return buf.toString();
}
}
/**
* <p>Returns padding using the specified delimiter repeated
* to a given length.</p>
*
* <pre>
* StringUtils.padding(0, 'e') = ""
* StringUtils.padding(3, 'e') = "eee"
* StringUtils.padding(-2, 'e') = IndexOutOfBoundsException
* </pre>
*
* <p>Note: this method doesn't not support padding with
* <a href="http://www.unicode.org/glossary/#supplementary_character">Unicode Supplementary Characters</a>
* as they require a pair of <code>char</code>s to be represented.
* If you are needing to support full I18N of your applications
* consider using {@link #repeat(String, int)} instead.
* </p>
*
* @param repeat number of times to repeat delim
* @param padChar character to repeat
* @return String with repeated character
* @throws IndexOutOfBoundsException if <code>repeat < 0</code>
* @see #repeat(String, int)
*/
private static String padding(int repeat, char padChar) throws IndexOutOfBoundsException {
if (repeat < 0) {
throw new IndexOutOfBoundsException("Cannot pad a negative amount: " + repeat);
}
final char[] buf = new char[repeat];
for (int i = 0; i < buf.length; i++) {
buf[i] = padChar;
}
return new String(buf);
}
public static boolean startsWith(final String extendee, final String prefix) {
final int length = prefix.length();
if (length >= extendee.length()) {
return false;
} else {
final char startingCharacter = extendee.charAt(length);
return extendee.startsWith(prefix) && Character.isUpperCase(startingCharacter);
}
}
public static String combinePath(final String extendee, final String suffix) {
if (Strings.isNullOrEmpty(extendee) && Strings.isNullOrEmpty(suffix)) {
return "";
}
if (Strings.isNullOrEmpty(extendee)) {
return suffix;
}
if (Strings.isNullOrEmpty(suffix)) {
return extendee;
}
if (extendee.endsWith("/") || suffix.startsWith("/")) {
return extendee + suffix;
}
return extendee + "/" + suffix;
}
/**
* Returns the name of a Java entity without any prefix. A prefix is defined
* as the first set of lowercase letters and the name is characters from,
* and including, the first upper case letter. If no upper case letter is
* found then an empty string is returned.
*
* <p>
* Calling this method with the following Java names will produce these
* results:
*
* <pre>
* getCarRegistration -> CarRegistration
* CityMayor -> CityMayor
* isReady -> Ready
* </pre>
*
*/
public static String asJavaBaseName(final String javaName) {
int pos = 0;
// find first upper case character
final int len = javaName.length();
while ((pos < len) && (javaName.charAt(pos) != '_') && Character.isLowerCase(javaName.charAt(pos))) {
pos++;
}
if (pos >= len) {
return "";
}
if (javaName.charAt(pos) == '_') {
pos++;
}
if (pos >= len) {
return "";
}
final String baseName = javaName.substring(pos);
final char firstChar = baseName.charAt(0);
if (Character.isLowerCase(firstChar)) {
return Character.toUpperCase(firstChar) + baseName.substring(1);
} else {
return baseName;
}
}
public static String asJavaBaseNameStripAccessorPrefixIfRequired(final String javaNameExtendee) {
if (javaNameExtendee.startsWith("is") || javaNameExtendee.startsWith("get")) {
return asJavaBaseName(javaNameExtendee);
} else {
return StringExtensions.asCapitalizedName(javaNameExtendee);
}
}
public static String asCapitalizedName(final String extendee) {
return Character.toUpperCase(extendee.charAt(0)) +
extendee.substring(1);
}
public static String asPluralName(final String extendee) {
String pluralName;
if (extendee.endsWith("y")) {
pluralName = extendee.substring(0, extendee.length() - 1) + "ies";
} else if (extendee.endsWith("s") || extendee.endsWith("x")) {
pluralName = extendee + "es";
} else {
pluralName = extendee + 's';
}
return pluralName;
}
public static String toCamelCase(final String extendee) {
final String nameLower = extendee.toLowerCase();
final StringBuilder buf = new StringBuilder();
boolean capitalizeNext = false;
for (int i = 0; i < nameLower.length(); i++) {
final char ch = nameLower.charAt(i);
if (ch == '_') {
capitalizeNext = true;
} else {
if (capitalizeNext) {
buf.append(Character.toUpperCase(ch));
} else {
buf.append(ch);
}
capitalizeNext = false;
}
}
return buf.toString();
}
}