001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.text; 018 019import java.util.HashSet; 020import java.util.Locale; 021import java.util.Set; 022 023import org.apache.commons.lang3.ArrayUtils; 024import org.apache.commons.lang3.StringUtils; 025 026/** 027 * Case manipulation operations on Strings that contain words. 028 * 029 * <p>This class tries to handle {@code null} input gracefully. 030 * An exception will not be thrown for a {@code null} input. 031 * Each method documents its behavior in more detail.</p> 032 * 033 * @since 1.2 034 */ 035public class CaseUtils { 036 037 /** 038 * The code point for the space character ({@value}). 039 */ 040 private static final int CODE_POINT_SPACE = 32; 041 042 /** 043 * Converts all the delimiter separated words in a String into camelCase, 044 * that is each word is made up of a title case character and then a series of 045 * lowercase characters. 046 * 047 * <p>The delimiters represent a set of characters understood to separate words. 048 * The first non-delimiter character after a delimiter will be capitalized. The first String 049 * character may or may not be capitalized and it's determined by the user input for capitalizeFirstLetter 050 * variable.</p> 051 * 052 * <p>A {@code null} input String returns {@code null}.</p> 053 * 054 * <p>A input string with only delimiter characters returns {@code ""}.</p> 055 * 056 * Capitalization uses the Unicode title case, normally equivalent to 057 * upper case and cannot perform locale-sensitive mappings. 058 * 059 * <pre> 060 * CaseUtils.toCamelCase(null, false) = null 061 * CaseUtils.toCamelCase("", false, *) = "" 062 * CaseUtils.toCamelCase(*, false, null) = * 063 * CaseUtils.toCamelCase(*, true, new char[0]) = * 064 * CaseUtils.toCamelCase("To.Camel.Case", false, new char[]{'.'}) = "toCamelCase" 065 * CaseUtils.toCamelCase(" to @ Camel case", true, new char[]{'@'}) = "ToCamelCase" 066 * CaseUtils.toCamelCase(" @to @ Camel case", false, new char[]{'@'}) = "toCamelCase" 067 * CaseUtils.toCamelCase(" @", false, new char[]{'@'}) = "" 068 * </pre> 069 * 070 * @param str The String to be converted to camelCase, may be null 071 * @param capitalizeFirstLetter boolean that determines if the first character of first word should be title case. 072 * @param delimiters set of characters to determine capitalization, null and/or empty array means whitespace 073 * @return camelCase of String, {@code null} if null String input 074 */ 075 public static String toCamelCase(String str, final boolean capitalizeFirstLetter, final char... delimiters) { 076 if (StringUtils.isEmpty(str)) { 077 return str; 078 } 079 str = str.toLowerCase(Locale.ROOT); 080 final int strLen = str.length(); 081 final int[] newCodePoints = new int[strLen]; 082 int outOffset = 0; 083 final Set<Integer> delimiterSet = toDelimiterSet(delimiters); 084 boolean capitalizeNext = capitalizeFirstLetter; 085 for (int index = 0; index < strLen;) { 086 final int codePoint = str.codePointAt(index); 087 if (delimiterSet.contains(codePoint)) { 088 capitalizeNext = outOffset != 0; 089 index += Character.charCount(codePoint); 090 } else if (capitalizeNext || outOffset == 0 && capitalizeFirstLetter) { 091 final int titleCaseCodePoint = Character.toTitleCase(codePoint); 092 newCodePoints[outOffset++] = titleCaseCodePoint; 093 index += Character.charCount(titleCaseCodePoint); 094 capitalizeNext = false; 095 } else { 096 newCodePoints[outOffset++] = codePoint; 097 index += Character.charCount(codePoint); 098 } 099 } 100 return new String(newCodePoints, 0, outOffset); 101 } 102 103 /** 104 * Converts an array of delimiters to a hash set of code points. Code point of space(32) is added 105 * as the default value. The generated hash set provides O(1) lookup time. 106 * 107 * @param delimiters set of characters to determine capitalization, null means whitespace 108 * @return Set<Integer> 109 */ 110 private static Set<Integer> toDelimiterSet(final char[] delimiters) { 111 final Set<Integer> delimiterHashSet = new HashSet<>(); 112 delimiterHashSet.add(CODE_POINT_SPACE); 113 if (ArrayUtils.isEmpty(delimiters)) { 114 return delimiterHashSet; 115 } 116 for (int index = 0; index < delimiters.length; index++) { 117 delimiterHashSet.add(Character.codePointAt(delimiters, index)); 118 } 119 return delimiterHashSet; 120 } 121 122 /** 123 * {@code CaseUtils} instances should NOT be constructed in 124 * standard programming. Instead, the class should be used as 125 * {@code CaseUtils.toCamelCase("foo bar", true, new char[]{'-'});}. 126 * 127 * <p>This constructor is public to permit tools that require a JavaBean 128 * instance to operate.</p> 129 */ 130 public CaseUtils() { 131 } 132} 133