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