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