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