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    
019    /**
020     * Utility class for converting between various ASCII case formats.
021     *
022     * @author Mike Bostock
023     * @since 1
024     */
025    @GwtCompatible
026    public enum CaseFormat {
027      /**
028       * Hyphenated variable naming convention, e.g., "lower-hyphen".
029       */
030      LOWER_HYPHEN(CharMatcher.is('-'), "-"),
031    
032      /**
033       * C++ variable naming convention, e.g., "lower_underscore".
034       */
035      LOWER_UNDERSCORE(CharMatcher.is('_'), "_"),
036    
037      /**
038       * Java variable naming convention, e.g., "lowerCamel".
039       */
040      LOWER_CAMEL(CharMatcher.inRange('A', 'Z'), ""),
041    
042      /**
043       * Java and C++ class naming convention, e.g., "UpperCamel".
044       */
045      UPPER_CAMEL(CharMatcher.inRange('A', 'Z'), ""),
046    
047      /**
048       * Java and C++ constant naming convention, e.g., "UPPER_UNDERSCORE".
049       */
050      UPPER_UNDERSCORE(CharMatcher.is('_'), "_");
051    
052      private final CharMatcher wordBoundary;
053      private final String wordSeparator;
054    
055      CaseFormat(CharMatcher wordBoundary, String wordSeparator) {
056        this.wordBoundary = wordBoundary;
057        this.wordSeparator = wordSeparator;
058      }
059    
060      /**
061       * Converts the specified {@code String s} from this format to the specified {@code format}. A
062       * "best effort" approach is taken; if {@code s} does not conform to the assumed format, then the
063       * behavior of this method is undefined but we make a reasonable effort at converting anyway.
064       */
065      public String to(CaseFormat format, String s) {
066        if (format == null) {
067          throw new NullPointerException();
068        }
069        if (s == null) {
070          throw new NullPointerException();
071        }
072    
073        if (format == this) {
074          return s;
075        }
076    
077        /* optimize cases where no camel conversion is required */
078        switch (this) {
079          case LOWER_HYPHEN:
080            switch (format) {
081              case LOWER_UNDERSCORE:
082                return s.replace('-', '_');
083              case UPPER_UNDERSCORE:
084                return Ascii.toUpperCase(s.replace('-', '_'));
085            }
086            break;
087          case LOWER_UNDERSCORE:
088            switch (format) {
089              case LOWER_HYPHEN:
090                return s.replace('_', '-');
091              case UPPER_UNDERSCORE:
092                return Ascii.toUpperCase(s);
093            }
094            break;
095          case UPPER_UNDERSCORE:
096            switch (format) {
097              case LOWER_HYPHEN:
098                return Ascii.toLowerCase(s.replace('_', '-'));
099              case LOWER_UNDERSCORE:
100                return Ascii.toLowerCase(s);
101            }
102            break;
103        }
104    
105        // otherwise, deal with camel conversion
106        StringBuilder out = null;
107        int i = 0;
108        int j = -1;
109        while ((j = wordBoundary.indexIn(s, ++j)) != -1) {
110          if (i == 0) {
111            // include some extra space for separators
112            out = new StringBuilder(s.length() + 4 * wordSeparator.length());
113            out.append(format.normalizeFirstWord(s.substring(i, j)));
114          } else {
115            out.append(format.normalizeWord(s.substring(i, j)));
116          }
117          out.append(format.wordSeparator);
118          i = j + wordSeparator.length();
119        }
120        if (i == 0) {
121          return format.normalizeFirstWord(s);
122        }
123        out.append(format.normalizeWord(s.substring(i)));
124        return out.toString();
125      }
126    
127      private String normalizeFirstWord(String word) {
128        switch (this) {
129          case LOWER_CAMEL:
130            return Ascii.toLowerCase(word);
131          default:
132            return normalizeWord(word);
133        }
134      }
135    
136      private String normalizeWord(String word) {
137        switch (this) {
138          case LOWER_HYPHEN:
139            return Ascii.toLowerCase(word);
140          case LOWER_UNDERSCORE:
141            return Ascii.toLowerCase(word);
142          case LOWER_CAMEL:
143            return firstCharOnlyToUpper(word);
144          case UPPER_CAMEL:
145            return firstCharOnlyToUpper(word);
146          case UPPER_UNDERSCORE:
147            return Ascii.toUpperCase(word);
148        }
149        throw new RuntimeException("unknown case: " + this);
150      }
151    
152      private static String firstCharOnlyToUpper(String word) {
153        int length = word.length();
154        if (length == 0) {
155          return word;
156        }
157        return new StringBuilder(length)
158            .append(Ascii.toUpperCase(word.charAt(0)))
159            .append(Ascii.toLowerCase(word.substring(1)))
160            .toString();
161      }
162    }