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 }