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 }