1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.text; 18 19 import java.util.HashSet; 20 import java.util.Set; 21 22 import org.apache.commons.lang3.ArrayUtils; 23 import org.apache.commons.lang3.StringUtils; 24 25 /** 26 * Case manipulation operations on Strings that contain words. 27 * 28 * <p>This class tries to handle {@code null} input gracefully. 29 * An exception will not be thrown for a {@code null} input. 30 * Each method documents its behavior in more detail.</p> 31 * 32 * @since 1.2 33 */ 34 public class CaseUtils { 35 36 /** 37 * Converts all the delimiter separated words in a String into camelCase, 38 * that is each word is made up of a title case character and then a series of 39 * lowercase characters. 40 * 41 * <p>The delimiters represent a set of characters understood to separate words. 42 * The first non-delimiter character after a delimiter will be capitalized. The first String 43 * character may or may not be capitalized and it's determined by the user input for capitalizeFirstLetter 44 * variable.</p> 45 * 46 * <p>A {@code null} input String returns {@code null}.</p> 47 * 48 * <p>A input string with only delimiter characters returns {@code ""}.</p> 49 * 50 * Capitalization uses the Unicode title case, normally equivalent to 51 * upper case and cannot perform locale-sensitive mappings. 52 * 53 * <pre> 54 * CaseUtils.toCamelCase(null, false) = null 55 * CaseUtils.toCamelCase("", false, *) = "" 56 * CaseUtils.toCamelCase(*, false, null) = * 57 * CaseUtils.toCamelCase(*, true, new char[0]) = * 58 * CaseUtils.toCamelCase("To.Camel.Case", false, new char[]{'.'}) = "toCamelCase" 59 * CaseUtils.toCamelCase(" to @ Camel case", true, new char[]{'@'}) = "ToCamelCase" 60 * CaseUtils.toCamelCase(" @to @ Camel case", false, new char[]{'@'}) = "toCamelCase" 61 * CaseUtils.toCamelCase(" @", false, new char[]{'@'}) = "" 62 * </pre> 63 * 64 * @param str the String to be converted to camelCase, may be null 65 * @param capitalizeFirstLetter boolean that determines if the first character of first word should be title case. 66 * @param delimiters set of characters to determine capitalization, null and/or empty array means whitespace 67 * @return camelCase of String, {@code null} if null String input 68 */ 69 public static String toCamelCase(String str, final boolean capitalizeFirstLetter, final char... delimiters) { 70 if (StringUtils.isEmpty(str)) { 71 return str; 72 } 73 str = str.toLowerCase(); 74 final int strLen = str.length(); 75 final int[] newCodePoints = new int[strLen]; 76 int outOffset = 0; 77 final Set<Integer> delimiterSet = toDelimiterSet(delimiters); 78 boolean capitalizeNext = capitalizeFirstLetter; 79 for (int index = 0; index < strLen;) { 80 final int codePoint = str.codePointAt(index); 81 82 if (delimiterSet.contains(codePoint)) { 83 capitalizeNext = outOffset != 0; 84 index += Character.charCount(codePoint); 85 } else if (capitalizeNext || outOffset == 0 && capitalizeFirstLetter) { 86 final int titleCaseCodePoint = Character.toTitleCase(codePoint); 87 newCodePoints[outOffset++] = titleCaseCodePoint; 88 index += Character.charCount(titleCaseCodePoint); 89 capitalizeNext = false; 90 } else { 91 newCodePoints[outOffset++] = codePoint; 92 index += Character.charCount(codePoint); 93 } 94 } 95 96 return new String(newCodePoints, 0, outOffset); 97 } 98 99 /** 100 * Converts an array of delimiters to a hash set of code points. Code point of space(32) is added 101 * as the default value. The generated hash set provides O(1) lookup time. 102 * 103 * @param delimiters set of characters to determine capitalization, null means whitespace 104 * @return Set<Integer> 105 */ 106 private static Set<Integer> toDelimiterSet(final char[] delimiters) { 107 final Set<Integer> delimiterHashSet = new HashSet<>(); 108 delimiterHashSet.add(Character.codePointAt(new char[]{' '}, 0)); 109 if (ArrayUtils.isEmpty(delimiters)) { 110 return delimiterHashSet; 111 } 112 113 for (int index = 0; index < delimiters.length; index++) { 114 delimiterHashSet.add(Character.codePointAt(delimiters, index)); 115 } 116 return delimiterHashSet; 117 } 118 119 /** 120 * {@code CaseUtils} instances should NOT be constructed in 121 * standard programming. Instead, the class should be used as 122 * {@code CaseUtils.toCamelCase("foo bar", true, new char[]{'-'});}. 123 * 124 * <p>This constructor is public to permit tools that require a JavaBean 125 * instance to operate.</p> 126 */ 127 public CaseUtils() { 128 } 129 } 130