1 /* 2 * Copyright (C) 2006 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.base; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 21 import com.google.common.annotations.Beta; 22 import com.google.common.annotations.GwtCompatible; 23 24 import java.io.Serializable; 25 26 import javax.annotation.Nullable; 27 28 /** 29 * Utility class for converting between various ASCII case formats. Behavior is undefined for 30 * non-ASCII input. 31 * 32 * @author Mike Bostock 33 * @since 1.0 34 */ 35 @GwtCompatible 36 public enum CaseFormat { 37 /** 38 * Hyphenated variable naming convention, e.g., "lower-hyphen". 39 */ 40 LOWER_HYPHEN(CharMatcher.is('-'), "-") { normalizeWord(String word)41 @Override String normalizeWord(String word) { 42 return Ascii.toLowerCase(word); 43 } convert(CaseFormat format, String s)44 @Override String convert(CaseFormat format, String s) { 45 if (format == LOWER_UNDERSCORE) { 46 return s.replace('-', '_'); 47 } 48 if (format == UPPER_UNDERSCORE) { 49 return Ascii.toUpperCase(s.replace('-', '_')); 50 } 51 return super.convert(format, s); 52 } 53 }, 54 55 /** 56 * C++ variable naming convention, e.g., "lower_underscore". 57 */ 58 LOWER_UNDERSCORE(CharMatcher.is('_'), "_") { normalizeWord(String word)59 @Override String normalizeWord(String word) { 60 return Ascii.toLowerCase(word); 61 } convert(CaseFormat format, String s)62 @Override String convert(CaseFormat format, String s) { 63 if (format == LOWER_HYPHEN) { 64 return s.replace('_', '-'); 65 } 66 if (format == UPPER_UNDERSCORE) { 67 return Ascii.toUpperCase(s); 68 } 69 return super.convert(format, s); 70 } 71 }, 72 73 /** 74 * Java variable naming convention, e.g., "lowerCamel". 75 */ 76 LOWER_CAMEL(CharMatcher.inRange('A', 'Z'), "") { normalizeWord(String word)77 @Override String normalizeWord(String word) { 78 return firstCharOnlyToUpper(word); 79 } 80 }, 81 82 /** 83 * Java and C++ class naming convention, e.g., "UpperCamel". 84 */ 85 UPPER_CAMEL(CharMatcher.inRange('A', 'Z'), "") { normalizeWord(String word)86 @Override String normalizeWord(String word) { 87 return firstCharOnlyToUpper(word); 88 } 89 }, 90 91 /** 92 * Java and C++ constant naming convention, e.g., "UPPER_UNDERSCORE". 93 */ 94 UPPER_UNDERSCORE(CharMatcher.is('_'), "_") { normalizeWord(String word)95 @Override String normalizeWord(String word) { 96 return Ascii.toUpperCase(word); 97 } convert(CaseFormat format, String s)98 @Override String convert(CaseFormat format, String s) { 99 if (format == LOWER_HYPHEN) { 100 return Ascii.toLowerCase(s.replace('_', '-')); 101 } 102 if (format == LOWER_UNDERSCORE) { 103 return Ascii.toLowerCase(s); 104 } 105 return super.convert(format, s); 106 } 107 }; 108 109 private final CharMatcher wordBoundary; 110 private final String wordSeparator; 111 CaseFormat(CharMatcher wordBoundary, String wordSeparator)112 CaseFormat(CharMatcher wordBoundary, String wordSeparator) { 113 this.wordBoundary = wordBoundary; 114 this.wordSeparator = wordSeparator; 115 } 116 117 /** 118 * Converts the specified {@code String str} from this format to the specified {@code format}. A 119 * "best effort" approach is taken; if {@code str} does not conform to the assumed format, then 120 * the behavior of this method is undefined but we make a reasonable effort at converting anyway. 121 */ to(CaseFormat format, String str)122 public final String to(CaseFormat format, String str) { 123 checkNotNull(format); 124 checkNotNull(str); 125 return (format == this) ? str : convert(format, str); 126 } 127 128 /** 129 * Enum values can override for performance reasons. 130 */ convert(CaseFormat format, String s)131 String convert(CaseFormat format, String s) { 132 // deal with camel conversion 133 StringBuilder out = null; 134 int i = 0; 135 int j = -1; 136 while ((j = wordBoundary.indexIn(s, ++j)) != -1) { 137 if (i == 0) { 138 // include some extra space for separators 139 out = new StringBuilder(s.length() + 4 * wordSeparator.length()); 140 out.append(format.normalizeFirstWord(s.substring(i, j))); 141 } else { 142 out.append(format.normalizeWord(s.substring(i, j))); 143 } 144 out.append(format.wordSeparator); 145 i = j + wordSeparator.length(); 146 } 147 return (i == 0) 148 ? format.normalizeFirstWord(s) 149 : out.append(format.normalizeWord(s.substring(i))).toString(); 150 } 151 152 /** 153 * Returns a {@code Converter} that converts strings from this format to {@code targetFormat}. 154 * 155 * @since 16.0 156 */ 157 @Beta converterTo(CaseFormat targetFormat)158 public Converter<String, String> converterTo(CaseFormat targetFormat) { 159 return new StringConverter(this, targetFormat); 160 } 161 162 private static final class StringConverter 163 extends Converter<String, String> implements Serializable { 164 165 private final CaseFormat sourceFormat; 166 private final CaseFormat targetFormat; 167 StringConverter(CaseFormat sourceFormat, CaseFormat targetFormat)168 StringConverter(CaseFormat sourceFormat, CaseFormat targetFormat) { 169 this.sourceFormat = checkNotNull(sourceFormat); 170 this.targetFormat = checkNotNull(targetFormat); 171 } 172 doForward(String s)173 @Override protected String doForward(String s) { 174 // TODO(kevinb): remove null boilerplate (convert() will do it automatically) 175 return s == null ? null : sourceFormat.to(targetFormat, s); 176 } 177 doBackward(String s)178 @Override protected String doBackward(String s) { 179 // TODO(kevinb): remove null boilerplate (convert() will do it automatically) 180 return s == null ? null : targetFormat.to(sourceFormat, s); 181 } 182 equals(@ullable Object object)183 @Override public boolean equals(@Nullable Object object) { 184 if (object instanceof StringConverter) { 185 StringConverter that = (StringConverter) object; 186 return sourceFormat.equals(that.sourceFormat) 187 && targetFormat.equals(that.targetFormat); 188 } 189 return false; 190 } 191 hashCode()192 @Override public int hashCode() { 193 return sourceFormat.hashCode() ^ targetFormat.hashCode(); 194 } 195 toString()196 @Override public String toString() { 197 return sourceFormat + ".converterTo(" + targetFormat + ")"; 198 } 199 200 private static final long serialVersionUID = 0L; 201 } 202 normalizeWord(String word)203 abstract String normalizeWord(String word); 204 normalizeFirstWord(String word)205 private String normalizeFirstWord(String word) { 206 return (this == LOWER_CAMEL) ? Ascii.toLowerCase(word) : normalizeWord(word); 207 } 208 firstCharOnlyToUpper(String word)209 private static String firstCharOnlyToUpper(String word) { 210 return (word.isEmpty()) 211 ? word 212 : new StringBuilder(word.length()) 213 .append(Ascii.toUpperCase(word.charAt(0))) 214 .append(Ascii.toLowerCase(word.substring(1))) 215 .toString(); 216 } 217 } 218