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 017package com.google.common.base; 018 019import static com.google.common.base.Preconditions.checkNotNull; 020 021import com.google.common.annotations.Beta; 022import com.google.common.annotations.GwtCompatible; 023 024import java.io.Serializable; 025 026import javax.annotation.CheckReturnValue; 027import javax.annotation.Nullable; 028 029/** 030 * Utility class for converting between various ASCII case formats. Behavior is undefined for 031 * non-ASCII input. 032 * 033 * @author Mike Bostock 034 * @since 1.0 035 */ 036@CheckReturnValue 037@GwtCompatible 038public enum CaseFormat { 039 /** 040 * Hyphenated variable naming convention, e.g., "lower-hyphen". 041 */ 042 LOWER_HYPHEN(CharMatcher.is('-'), "-") { 043 @Override 044 String normalizeWord(String word) { 045 return Ascii.toLowerCase(word); 046 } 047 048 @Override 049 String convert(CaseFormat format, String s) { 050 if (format == LOWER_UNDERSCORE) { 051 return s.replace('-', '_'); 052 } 053 if (format == UPPER_UNDERSCORE) { 054 return Ascii.toUpperCase(s.replace('-', '_')); 055 } 056 return super.convert(format, s); 057 } 058 }, 059 060 /** 061 * C++ variable naming convention, e.g., "lower_underscore". 062 */ 063 LOWER_UNDERSCORE(CharMatcher.is('_'), "_") { 064 @Override 065 String normalizeWord(String word) { 066 return Ascii.toLowerCase(word); 067 } 068 069 @Override 070 String convert(CaseFormat format, String s) { 071 if (format == LOWER_HYPHEN) { 072 return s.replace('_', '-'); 073 } 074 if (format == UPPER_UNDERSCORE) { 075 return Ascii.toUpperCase(s); 076 } 077 return super.convert(format, s); 078 } 079 }, 080 081 /** 082 * Java variable naming convention, e.g., "lowerCamel". 083 */ 084 LOWER_CAMEL(CharMatcher.inRange('A', 'Z'), "") { 085 @Override 086 String normalizeWord(String word) { 087 return firstCharOnlyToUpper(word); 088 } 089 }, 090 091 /** 092 * Java and C++ class naming convention, e.g., "UpperCamel". 093 */ 094 UPPER_CAMEL(CharMatcher.inRange('A', 'Z'), "") { 095 @Override 096 String normalizeWord(String word) { 097 return firstCharOnlyToUpper(word); 098 } 099 }, 100 101 /** 102 * Java and C++ constant naming convention, e.g., "UPPER_UNDERSCORE". 103 */ 104 UPPER_UNDERSCORE(CharMatcher.is('_'), "_") { 105 @Override 106 String normalizeWord(String word) { 107 return Ascii.toUpperCase(word); 108 } 109 110 @Override 111 String convert(CaseFormat format, String s) { 112 if (format == LOWER_HYPHEN) { 113 return Ascii.toLowerCase(s.replace('_', '-')); 114 } 115 if (format == LOWER_UNDERSCORE) { 116 return Ascii.toLowerCase(s); 117 } 118 return super.convert(format, s); 119 } 120 }; 121 122 private final CharMatcher wordBoundary; 123 private final String wordSeparator; 124 125 CaseFormat(CharMatcher wordBoundary, String wordSeparator) { 126 this.wordBoundary = wordBoundary; 127 this.wordSeparator = wordSeparator; 128 } 129 130 /** 131 * Converts the specified {@code String str} from this format to the specified {@code format}. A 132 * "best effort" approach is taken; if {@code str} does not conform to the assumed format, then 133 * the behavior of this method is undefined but we make a reasonable effort at converting anyway. 134 */ 135 public final String to(CaseFormat format, String str) { 136 checkNotNull(format); 137 checkNotNull(str); 138 return (format == this) ? str : convert(format, str); 139 } 140 141 /** 142 * Enum values can override for performance reasons. 143 */ 144 String convert(CaseFormat format, String s) { 145 // deal with camel conversion 146 StringBuilder out = null; 147 int i = 0; 148 int j = -1; 149 while ((j = wordBoundary.indexIn(s, ++j)) != -1) { 150 if (i == 0) { 151 // include some extra space for separators 152 out = new StringBuilder(s.length() + 4 * wordSeparator.length()); 153 out.append(format.normalizeFirstWord(s.substring(i, j))); 154 } else { 155 out.append(format.normalizeWord(s.substring(i, j))); 156 } 157 out.append(format.wordSeparator); 158 i = j + wordSeparator.length(); 159 } 160 return (i == 0) 161 ? format.normalizeFirstWord(s) 162 : out.append(format.normalizeWord(s.substring(i))).toString(); 163 } 164 165 /** 166 * Returns a {@code Converter} that converts strings from this format to {@code targetFormat}. 167 * 168 * @since 16.0 169 */ 170 @Beta 171 public Converter<String, String> converterTo(CaseFormat targetFormat) { 172 return new StringConverter(this, targetFormat); 173 } 174 175 private static final class StringConverter extends Converter<String, String> 176 implements Serializable { 177 178 private final CaseFormat sourceFormat; 179 private final CaseFormat targetFormat; 180 181 StringConverter(CaseFormat sourceFormat, CaseFormat targetFormat) { 182 this.sourceFormat = checkNotNull(sourceFormat); 183 this.targetFormat = checkNotNull(targetFormat); 184 } 185 186 @Override 187 protected String doForward(String s) { 188 return sourceFormat.to(targetFormat, s); 189 } 190 191 @Override 192 protected String doBackward(String s) { 193 return targetFormat.to(sourceFormat, s); 194 } 195 196 @Override 197 public boolean equals(@Nullable Object object) { 198 if (object instanceof StringConverter) { 199 StringConverter that = (StringConverter) object; 200 return sourceFormat.equals(that.sourceFormat) && targetFormat.equals(that.targetFormat); 201 } 202 return false; 203 } 204 205 @Override 206 public int hashCode() { 207 return sourceFormat.hashCode() ^ targetFormat.hashCode(); 208 } 209 210 @Override 211 public String toString() { 212 return sourceFormat + ".converterTo(" + targetFormat + ")"; 213 } 214 215 private static final long serialVersionUID = 0L; 216 } 217 218 abstract String normalizeWord(String word); 219 220 private String normalizeFirstWord(String word) { 221 return (this == LOWER_CAMEL) ? Ascii.toLowerCase(word) : normalizeWord(word); 222 } 223 224 private static String firstCharOnlyToUpper(String word) { 225 return (word.isEmpty()) 226 ? word 227 : new StringBuilder(word.length()) 228 .append(Ascii.toUpperCase(word.charAt(0))) 229 .append(Ascii.toLowerCase(word.substring(1))) 230 .toString(); 231 } 232}