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 * http://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 org.apache.commons.lang3.StringUtils; 020 021import java.util.HashSet; 022import java.util.Set; 023 024/** 025 * <p>Case manipulation operations on Strings that contain words.</p> 026 * 027 * <p>This class tries to handle <code>null</code> input gracefully. 028 * An exception will not be thrown for a <code>null</code> input. 029 * Each method documents its behaviour in more detail.</p> 030 * 031 * @since 1.2 032 */ 033public class CaseUtils { 034 035 /** 036 * <p><code>CaseUtils</code> instances should NOT be constructed in 037 * standard programming. Instead, the class should be used as 038 * <code>CaseUtils.toCamelCase("foo bar", true, new char[]{'-'});</code>.</p> 039 * 040 * <p>This constructor is public to permit tools that require a JavaBean 041 * instance to operate.</p> 042 */ 043 public CaseUtils() { 044 super(); 045 } 046 047 /** 048 * <p>Converts all the delimiter separated words in a String into camelCase, 049 * that is each word is made up of a titlecase character and then a series of 050 * lowercase characters.</p> 051 * 052 * <p>The delimiters represent a set of characters understood to separate words. 053 * The first non-delimiter character after a delimiter will be capitalized. The first String 054 * character may or may not be capitalized and it's determined by the user input for capitalizeFirstLetter 055 * variable.</p> 056 * 057 * <p>A <code>null</code> input String returns <code>null</code>. 058 * Capitalization uses the Unicode title case, normally equivalent to 059 * upper case and cannot perform locale-sensitive mappings.</p> 060 * 061 * <pre> 062 * CaseUtils.toCamelCase(null, false) = null 063 * CaseUtils.toCamelCase("", false, *) = "" 064 * CaseUtils.toCamelCase(*, false, null) = * 065 * CaseUtils.toCamelCase(*, true, new char[0]) = * 066 * CaseUtils.toCamelCase("To.Camel.Case", false, new char[]{'.'}) = "toCamelCase" 067 * CaseUtils.toCamelCase(" to @ Camel case", true, new char[]{'@'}) = "ToCamelCase" 068 * CaseUtils.toCamelCase(" @to @ Camel case", false, new char[]{'@'}) = "toCamelCase" 069 * </pre> 070 * 071 * @param str the String to be converted to camelCase, may be null 072 * @param capitalizeFirstLetter boolean that determines if the first character of first word should be title case. 073 * @param delimiters set of characters to determine capitalization, null and/or empty array means whitespace 074 * @return camelCase of String, <code>null</code> if null String input 075 */ 076 public static String toCamelCase(String str, final boolean capitalizeFirstLetter, final char... delimiters) { 077 if (StringUtils.isEmpty(str)) { 078 return str; 079 } 080 str = str.toLowerCase(); 081 final int strLen = str.length(); 082 final int[] newCodePoints = new int[strLen]; 083 int outOffset = 0; 084 final Set<Integer> delimiterSet = generateDelimiterSet(delimiters); 085 boolean capitalizeNext = false; 086 if (capitalizeFirstLetter) { 087 capitalizeNext = true; 088 } 089 for (int index = 0; index < strLen;) { 090 final int codePoint = str.codePointAt(index); 091 092 if (delimiterSet.contains(codePoint)) { 093 capitalizeNext = true; 094 if (outOffset == 0) { 095 capitalizeNext = false; 096 } 097 index += Character.charCount(codePoint); 098 } else if (capitalizeNext || outOffset == 0 && capitalizeFirstLetter) { 099 final int titleCaseCodePoint = Character.toTitleCase(codePoint); 100 newCodePoints[outOffset++] = titleCaseCodePoint; 101 index += Character.charCount(titleCaseCodePoint); 102 capitalizeNext = false; 103 } else { 104 newCodePoints[outOffset++] = codePoint; 105 index += Character.charCount(codePoint); 106 } 107 } 108 if (outOffset != 0) { 109 return new String(newCodePoints, 0, outOffset); 110 } 111 return str; 112 } 113 114 /** 115 * <p>Converts an array of delimiters to a hash set of code points. Code point of space(32) is added 116 * as the default value. The generated hash set provides O(1) lookup time.</p> 117 * 118 * @param delimiters set of characters to determine capitalization, null means whitespace 119 * @return Set<Integer> 120 */ 121 private static Set<Integer> generateDelimiterSet(final char[] delimiters) { 122 final Set<Integer> delimiterHashSet = new HashSet<>(); 123 delimiterHashSet.add(Character.codePointAt(new char[]{' '}, 0)); 124 if (delimiters == null || delimiters.length == 0) { 125 return delimiterHashSet; 126 } 127 128 for (int index = 0; index < delimiters.length; index++) { 129 delimiterHashSet.add(Character.codePointAt(delimiters, index)); 130 } 131 return delimiterHashSet; 132 } 133} 134