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