Home | History | Annotate | Download | only in base
      1 /*
      2  * Copyright (C) 2006 The Guava Authors
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.common.base;
     18 
     19 import static com.google.common.base.Preconditions.checkNotNull;
     20 
     21 import com.google.common.annotations.Beta;
     22 import com.google.common.annotations.GwtCompatible;
     23 
     24 import java.io.Serializable;
     25 
     26 import javax.annotation.Nullable;
     27 
     28 /**
     29  * Utility class for converting between various ASCII case formats. Behavior is undefined for
     30  * non-ASCII input.
     31  *
     32  * @author Mike Bostock
     33  * @since 1.0
     34  */
     35 @GwtCompatible
     36 public enum CaseFormat {
     37   /**
     38    * Hyphenated variable naming convention, e.g., "lower-hyphen".
     39    */
     40   LOWER_HYPHEN(CharMatcher.is('-'), "-") {
     41     @Override String normalizeWord(String word) {
     42       return Ascii.toLowerCase(word);
     43     }
     44     @Override String convert(CaseFormat format, String s) {
     45       if (format == LOWER_UNDERSCORE) {
     46         return s.replace('-', '_');
     47       }
     48       if (format == UPPER_UNDERSCORE) {
     49         return Ascii.toUpperCase(s.replace('-', '_'));
     50       }
     51       return super.convert(format, s);
     52     }
     53   },
     54 
     55   /**
     56    * C++ variable naming convention, e.g., "lower_underscore".
     57    */
     58   LOWER_UNDERSCORE(CharMatcher.is('_'), "_") {
     59     @Override String normalizeWord(String word) {
     60       return Ascii.toLowerCase(word);
     61     }
     62     @Override String convert(CaseFormat format, String s) {
     63       if (format == LOWER_HYPHEN) {
     64         return s.replace('_', '-');
     65       }
     66       if (format == UPPER_UNDERSCORE) {
     67         return Ascii.toUpperCase(s);
     68       }
     69       return super.convert(format, s);
     70     }
     71   },
     72 
     73   /**
     74    * Java variable naming convention, e.g., "lowerCamel".
     75    */
     76   LOWER_CAMEL(CharMatcher.inRange('A', 'Z'), "") {
     77     @Override String normalizeWord(String word) {
     78       return firstCharOnlyToUpper(word);
     79     }
     80   },
     81 
     82   /**
     83    * Java and C++ class naming convention, e.g., "UpperCamel".
     84    */
     85   UPPER_CAMEL(CharMatcher.inRange('A', 'Z'), "") {
     86     @Override String normalizeWord(String word) {
     87       return firstCharOnlyToUpper(word);
     88     }
     89   },
     90 
     91   /**
     92    * Java and C++ constant naming convention, e.g., "UPPER_UNDERSCORE".
     93    */
     94   UPPER_UNDERSCORE(CharMatcher.is('_'), "_") {
     95     @Override String normalizeWord(String word) {
     96       return Ascii.toUpperCase(word);
     97     }
     98     @Override String convert(CaseFormat format, String s) {
     99       if (format == LOWER_HYPHEN) {
    100         return Ascii.toLowerCase(s.replace('_', '-'));
    101       }
    102       if (format == LOWER_UNDERSCORE) {
    103         return Ascii.toLowerCase(s);
    104       }
    105       return super.convert(format, s);
    106     }
    107   };
    108 
    109   private final CharMatcher wordBoundary;
    110   private final String wordSeparator;
    111 
    112   CaseFormat(CharMatcher wordBoundary, String wordSeparator) {
    113     this.wordBoundary = wordBoundary;
    114     this.wordSeparator = wordSeparator;
    115   }
    116 
    117   /**
    118    * Converts the specified {@code String str} from this format to the specified {@code format}. A
    119    * "best effort" approach is taken; if {@code str} does not conform to the assumed format, then
    120    * the behavior of this method is undefined but we make a reasonable effort at converting anyway.
    121    */
    122   public final String to(CaseFormat format, String str) {
    123     checkNotNull(format);
    124     checkNotNull(str);
    125     return (format == this) ? str : convert(format, str);
    126   }
    127 
    128   /**
    129    * Enum values can override for performance reasons.
    130    */
    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 * wordSeparator.length());
    140         out.append(format.normalizeFirstWord(s.substring(i, j)));
    141       } else {
    142         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       : out.append(format.normalizeWord(s.substring(i))).toString();
    150   }
    151 
    152   /**
    153    * Returns a {@code Converter} that converts strings from this format to {@code targetFormat}.
    154    *
    155    * @since 16.0
    156    */
    157   @Beta
    158   public Converter<String, String> converterTo(CaseFormat targetFormat) {
    159     return new StringConverter(this, targetFormat);
    160   }
    161 
    162   private static final class StringConverter
    163       extends Converter<String, String> 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 protected String doForward(String s) {
    174       // TODO(kevinb): remove null boilerplate (convert() will do it automatically)
    175       return s == null ? null : sourceFormat.to(targetFormat, s);
    176     }
    177 
    178     @Override protected String doBackward(String s) {
    179       // TODO(kevinb): remove null boilerplate (convert() will do it automatically)
    180       return s == null ? null : targetFormat.to(sourceFormat, s);
    181     }
    182 
    183     @Override public boolean equals(@Nullable Object object) {
    184       if (object instanceof StringConverter) {
    185         StringConverter that = (StringConverter) object;
    186         return sourceFormat.equals(that.sourceFormat)
    187             && targetFormat.equals(that.targetFormat);
    188       }
    189       return false;
    190     }
    191 
    192     @Override public int hashCode() {
    193       return sourceFormat.hashCode() ^ targetFormat.hashCode();
    194     }
    195 
    196     @Override public String toString() {
    197       return sourceFormat + ".converterTo(" + targetFormat + ")";
    198     }
    199 
    200     private static final long serialVersionUID = 0L;
    201   }
    202 
    203   abstract String normalizeWord(String word);
    204 
    205   private String normalizeFirstWord(String word) {
    206     return (this == LOWER_CAMEL) ? Ascii.toLowerCase(word) : normalizeWord(word);
    207   }
    208 
    209   private static String firstCharOnlyToUpper(String word) {
    210     return (word.isEmpty())
    211         ? word
    212         : new StringBuilder(word.length())
    213             .append(Ascii.toUpperCase(word.charAt(0)))
    214             .append(Ascii.toLowerCase(word.substring(1)))
    215             .toString();
    216   }
    217 }
    218