001    /*
002     * Copyright (C) 2006 Google Inc.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005     * in compliance with the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the
010     * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
011     * express or implied. See the License for the specific language governing permissions and
012     * limitations under the License.
013     */
014    
015    package com.google.common.base;
016    
017    import com.google.common.annotations.GwtCompatible;
018    import com.google.common.annotations.VisibleForTesting;
019    
020    /**
021     * Utility class for converting between various ASCII case formats.
022     *
023     * @author Mike Bostock
024     * @since 1
025     */
026    @GwtCompatible
027    public enum CaseFormat {
028      /**
029       * Hyphenated variable naming convention, e.g., "lower-hyphen".
030       */
031      LOWER_HYPHEN(CharMatcher.is('-'), "-"),
032    
033      /**
034       * C++ variable naming convention, e.g., "lower_underscore".
035       */
036      LOWER_UNDERSCORE(CharMatcher.is('_'), "_"),
037    
038      /**
039       * Java variable naming convention, e.g., "lowerCamel".
040       */
041      LOWER_CAMEL(CharMatcher.inRange('A', 'Z'), ""),
042    
043      /**
044       * Java and C++ class naming convention, e.g., "UpperCamel".
045       */
046      UPPER_CAMEL(CharMatcher.inRange('A', 'Z'), ""),
047    
048      /**
049       * Java and C++ constant naming convention, e.g., "UPPER_UNDERSCORE".
050       */
051      UPPER_UNDERSCORE(CharMatcher.is('_'), "_");
052    
053      private final CharMatcher wordBoundary;
054      private final String wordSeparator;
055    
056      CaseFormat(CharMatcher wordBoundary, String wordSeparator) {
057        this.wordBoundary = wordBoundary;
058        this.wordSeparator = wordSeparator;
059      }
060    
061      /**
062       * Converts the specified {@code String s} from this format to the specified {@code format}. A
063       * "best effort" approach is taken; if {@code s} does not conform to the assumed format, then the
064       * behavior of this method is undefined but we make a reasonable effort at converting anyway.
065       */
066      public String to(CaseFormat format, String s) {
067        if (format == null) {
068          throw new NullPointerException();
069        }
070        if (s == null) {
071          throw new NullPointerException();
072        }
073    
074        if (format == this) {
075          return s;
076        }
077    
078        /* optimize cases where no camel conversion is required */
079        switch (this) {
080          case LOWER_HYPHEN:
081            switch (format) {
082              case LOWER_UNDERSCORE:
083                return s.replace('-', '_');
084              case UPPER_UNDERSCORE:
085                return toUpperCaseAscii(s.replace('-', '_'));
086            }
087            break;
088          case LOWER_UNDERSCORE:
089            switch (format) {
090              case LOWER_HYPHEN:
091                return s.replace('_', '-');
092              case UPPER_UNDERSCORE:
093                return toUpperCaseAscii(s);
094            }
095            break;
096          case UPPER_UNDERSCORE:
097            switch (format) {
098              case LOWER_HYPHEN:
099                return toLowerCaseAscii(s.replace('_', '-'));
100              case LOWER_UNDERSCORE:
101                return toLowerCaseAscii(s);
102            }
103            break;
104        }
105    
106        // otherwise, deal with camel conversion
107        StringBuilder out = null;
108        int i = 0;
109        int j = -1;
110        while ((j = wordBoundary.indexIn(s, ++j)) != -1) {
111          if (i == 0) {
112            // include some extra space for separators
113            out = new StringBuilder(s.length() + 4 * wordSeparator.length());
114            out.append(format.normalizeFirstWord(s.substring(i, j)));
115          } else {
116            out.append(format.normalizeWord(s.substring(i, j)));
117          }
118          out.append(format.wordSeparator);
119          i = j + wordSeparator.length();
120        }
121        if (i == 0) {
122          return format.normalizeFirstWord(s);
123        }
124        out.append(format.normalizeWord(s.substring(i)));
125        return out.toString();
126      }
127    
128      private String normalizeFirstWord(String word) {
129        switch (this) {
130          case LOWER_CAMEL:
131            return toLowerCaseAscii(word);
132          default:
133            return normalizeWord(word);
134        }
135      }
136    
137      private String normalizeWord(String word) {
138        switch (this) {
139          case LOWER_HYPHEN:
140            return toLowerCaseAscii(word);
141          case LOWER_UNDERSCORE:
142            return toLowerCaseAscii(word);
143          case LOWER_CAMEL:
144            return firstCharOnlyToUpper(word);
145          case UPPER_CAMEL:
146            return firstCharOnlyToUpper(word);
147          case UPPER_UNDERSCORE:
148            return toUpperCaseAscii(word);
149        }
150        throw new RuntimeException("unknown case: " + this);
151      }
152    
153      private static String firstCharOnlyToUpper(String word) {
154        int length = word.length();
155        if (length == 0) {
156          return word;
157        }
158        return new StringBuilder(length)
159            .append(charToUpperCaseAscii(word.charAt(0)))
160            .append(toLowerCaseAscii(word.substring(1)))
161            .toString();
162      }
163    
164      @VisibleForTesting static String toUpperCaseAscii(String string) {
165        int length = string.length();
166        StringBuilder builder = new StringBuilder(length);
167        for (int i = 0; i < length; i++) {
168          builder.append(charToUpperCaseAscii(string.charAt(i)));
169        }
170        return builder.toString();
171      }
172    
173      @VisibleForTesting static String toLowerCaseAscii(String string) {
174        int length = string.length();
175        StringBuilder builder = new StringBuilder(length);
176        for (int i = 0; i < length; i++) {
177          builder.append(charToLowerCaseAscii(string.charAt(i)));
178        }
179        return builder.toString();
180      }
181    
182      private static char charToUpperCaseAscii(char c) {
183        return isLowerCase(c) ? (char) (c & 0x5f) : c;
184      }
185    
186      private static char charToLowerCaseAscii(char c) {
187        return isUpperCase(c) ? (char) (c ^ 0x20) : c;
188      }
189    
190      private static boolean isLowerCase(char c) {
191        return (c >= 'a') && (c <= 'z');
192      }
193    
194      private static boolean isUpperCase(char c) {
195        return (c >= 'A') && (c <= 'Z');
196      }
197    }