View Javadoc
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    *      https://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.Locale;
21  import java.util.Set;
22  
23  import org.apache.commons.lang3.ArrayUtils;
24  import org.apache.commons.lang3.StringUtils;
25  
26  /**
27   * Case manipulation operations on Strings that contain words.
28   *
29   * <p>This class tries to handle {@code null} input gracefully.
30   * An exception will not be thrown for a {@code null} input.
31   * Each method documents its behavior in more detail.</p>
32   *
33   * @since 1.2
34   */
35  public class CaseUtils {
36  
37      /**
38       * The code point for the space character ({@value}).
39       */
40      private static final int CODE_POINT_SPACE = 32;
41  
42      /**
43       * Converts all the delimiter separated words in a String into camelCase,
44       * that is each word is made up of a title case character and then a series of
45       * lowercase characters.
46       *
47       * <p>The delimiters represent a set of characters understood to separate words.
48       * The first non-delimiter character after a delimiter will be capitalized. The first String
49       * character may or may not be capitalized and it's determined by the user input for capitalizeFirstLetter
50       * variable.</p>
51       *
52       * <p>A {@code null} input String returns {@code null}.</p>
53       *
54       * <p>A input string with only delimiter characters returns {@code ""}.</p>
55       *
56       * Capitalization uses the Unicode title case, normally equivalent to
57       * upper case and cannot perform locale-sensitive mappings.
58       *
59       * <pre>
60       * CaseUtils.toCamelCase(null, false)                                 = null
61       * CaseUtils.toCamelCase("", false, *)                                = ""
62       * CaseUtils.toCamelCase(*, false, null)                              = *
63       * CaseUtils.toCamelCase(*, true, new char[0])                        = *
64       * CaseUtils.toCamelCase("To.Camel.Case", false, new char[]{'.'})     = "toCamelCase"
65       * CaseUtils.toCamelCase(" to @ Camel case", true, new char[]{'@'})   = "ToCamelCase"
66       * CaseUtils.toCamelCase(" @to @ Camel case", false, new char[]{'@'}) = "toCamelCase"
67       * CaseUtils.toCamelCase(" @", false, new char[]{'@'})                = ""
68       * </pre>
69       *
70       * @param str  The String to be converted to camelCase, may be null
71       * @param capitalizeFirstLetter boolean that determines if the first character of first word should be title case.
72       * @param delimiters  set of characters to determine capitalization, null and/or empty array means whitespace
73       * @return camelCase of String, {@code null} if null String input
74       */
75      public static String toCamelCase(String str, final boolean capitalizeFirstLetter, final char... delimiters) {
76          if (StringUtils.isEmpty(str)) {
77              return str;
78          }
79          str = str.toLowerCase(Locale.ROOT);
80          final int strLen = str.length();
81          final int[] newCodePoints = new int[strLen];
82          int outOffset = 0;
83          final Set<Integer> delimiterSet = toDelimiterSet(delimiters);
84          boolean capitalizeNext = capitalizeFirstLetter;
85          for (int index = 0; index < strLen;) {
86              final int codePoint = str.codePointAt(index);
87              if (delimiterSet.contains(codePoint)) {
88                  capitalizeNext = outOffset != 0;
89                  index += Character.charCount(codePoint);
90              } else if (capitalizeNext || outOffset == 0 && capitalizeFirstLetter) {
91                  final int titleCaseCodePoint = Character.toTitleCase(codePoint);
92                  newCodePoints[outOffset++] = titleCaseCodePoint;
93                  index += Character.charCount(titleCaseCodePoint);
94                  capitalizeNext = false;
95              } else {
96                  newCodePoints[outOffset++] = codePoint;
97                  index += Character.charCount(codePoint);
98              }
99          }
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