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