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 java.util.HashSet;
020import java.util.Set;
021
022import org.apache.commons.lang3.ArrayUtils;
023import org.apache.commons.lang3.StringUtils;
024
025/**
026 * Case manipulation operations on Strings that contain words.
027 *
028 * <p>This class tries to handle {@code null} input gracefully.
029 * An exception will not be thrown for a {@code null} input.
030 * Each method documents its behavior in more detail.</p>
031 *
032 * @since 1.2
033 */
034public class CaseUtils {
035
036    /**
037     * Converts all the delimiter separated words in a String into camelCase,
038     * that is each word is made up of a title case character and then a series of
039     * lowercase characters.
040     *
041     * <p>The delimiters represent a set of characters understood to separate words.
042     * The first non-delimiter character after a delimiter will be capitalized. The first String
043     * character may or may not be capitalized and it's determined by the user input for capitalizeFirstLetter
044     * variable.</p>
045     *
046     * <p>A {@code null} input String returns {@code null}.</p>
047     *
048     * <p>A input string with only delimiter characters returns {@code ""}.</p>
049     *
050     * Capitalization uses the Unicode title case, normally equivalent to
051     * upper case and cannot perform locale-sensitive mappings.
052     *
053     * <pre>
054     * CaseUtils.toCamelCase(null, false)                                 = null
055     * CaseUtils.toCamelCase("", false, *)                                = ""
056     * CaseUtils.toCamelCase(*, false, null)                              = *
057     * CaseUtils.toCamelCase(*, true, new char[0])                        = *
058     * CaseUtils.toCamelCase("To.Camel.Case", false, new char[]{'.'})     = "toCamelCase"
059     * CaseUtils.toCamelCase(" to @ Camel case", true, new char[]{'@'})   = "ToCamelCase"
060     * CaseUtils.toCamelCase(" @to @ Camel case", false, new char[]{'@'}) = "toCamelCase"
061     * CaseUtils.toCamelCase(" @", false, new char[]{'@'})                = ""
062     * </pre>
063     *
064     * @param str  the String to be converted to camelCase, may be null
065     * @param capitalizeFirstLetter boolean that determines if the first character of first word should be title case.
066     * @param delimiters  set of characters to determine capitalization, null and/or empty array means whitespace
067     * @return camelCase of String, {@code null} if null String input
068     */
069    public static String toCamelCase(String str, final boolean capitalizeFirstLetter, final char... delimiters) {
070        if (StringUtils.isEmpty(str)) {
071            return str;
072        }
073        str = str.toLowerCase();
074        final int strLen = str.length();
075        final int[] newCodePoints = new int[strLen];
076        int outOffset = 0;
077        final Set<Integer> delimiterSet = toDelimiterSet(delimiters);
078        boolean capitalizeNext = capitalizeFirstLetter;
079        for (int index = 0; index < strLen;) {
080            final int codePoint = str.codePointAt(index);
081
082            if (delimiterSet.contains(codePoint)) {
083                capitalizeNext = outOffset != 0;
084                index += Character.charCount(codePoint);
085            } else if (capitalizeNext || outOffset == 0 && capitalizeFirstLetter) {
086                final int titleCaseCodePoint = Character.toTitleCase(codePoint);
087                newCodePoints[outOffset++] = titleCaseCodePoint;
088                index += Character.charCount(titleCaseCodePoint);
089                capitalizeNext = false;
090            } else {
091                newCodePoints[outOffset++] = codePoint;
092                index += Character.charCount(codePoint);
093            }
094        }
095
096        return new String(newCodePoints, 0, outOffset);
097    }
098
099    /**
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