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