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