1 /*
   2  * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.codemodel.internal;
  27 
  28 import java.util.HashSet;
  29 import java.util.regex.Matcher;
  30 import java.util.regex.Pattern;
  31 
  32 /**
  33  * Utility methods that convert arbitrary strings into Java identifiers.
  34  */
  35 public class JJavaName {
  36 
  37 
  38     /**
  39      * Checks if a given string is usable as a Java identifier.
  40      */
  41     public static boolean isJavaIdentifier(String s) {
  42         if(s.length()==0)   return false;
  43         if( reservedKeywords.contains(s) )  return false;
  44 
  45         if(!Character.isJavaIdentifierStart(s.charAt(0)))   return false;
  46 
  47         for (int i = 1; i < s.length(); i++)
  48             if (!Character.isJavaIdentifierPart(s.charAt(i)))
  49                 return false;
  50 
  51         return true;
  52     }
  53 
  54     /**
  55      * Checks if the given string is a valid fully qualified name.
  56      */
  57     public static boolean isFullyQualifiedClassName(String s) {
  58         return isJavaPackageName(s);
  59     }
  60 
  61     /**
  62      * Checks if the given string is a valid Java package name.
  63      */
  64     public static boolean isJavaPackageName(String s) {
  65         while(s.length()!=0) {
  66             int idx = s.indexOf('.');
  67             if(idx==-1) idx=s.length();
  68             if( !isJavaIdentifier(s.substring(0,idx)) )
  69                 return false;
  70 
  71             s = s.substring(idx);
  72             if(s.length()!=0)    s = s.substring(1);    // remove '.'
  73         }
  74         return true;
  75     }
  76 
  77     /**
  78      * <b>Experimental API:</b> converts an English word into a plural form.
  79      *
  80      * @param word
  81      *      a word, such as "child", "apple". Must not be null.
  82      *      It accepts word concatanation forms
  83      *      that are common in programming languages, such as "my_child", "MyChild",
  84      *      "myChild", "MY-CHILD", "CODE003-child", etc, and mostly tries to do the right thing.
  85      *      ("my_children","MyChildren","myChildren", and "MY-CHILDREN", "CODE003-children" respectively)
  86      *      <p>
  87      *      Although this method only works for English words, it handles non-English
  88      *      words gracefully (by just returning it as-is.) For example, 日本語
  89      *      will be returned as-is without modified, not "日本語s"
  90      *      <p>
  91      *      This method doesn't handle suffixes very well. For example, passing
  92      *      "person56" will return "person56s", not "people56".
  93      *
  94      * @return
  95      *      always non-null.
  96      */
  97     public static String getPluralForm(String word) {
  98         // remember the casing of the word
  99         boolean allUpper = true;
 100 
 101         // check if the word looks like an English word.
 102         // if we see non-ASCII characters, abort
 103         for(int i=0; i<word.length(); i++ ) {
 104             char ch = word.charAt(i);
 105             if(ch >=0x80)
 106                 return word;
 107 
 108             // note that this isn't the same as allUpper &= Character.isUpperCase(ch);
 109             allUpper &= !Character.isLowerCase(ch);
 110         }
 111 
 112         for (Entry e : TABLE) {
 113             String r = e.apply(word);
 114             if(r!=null) {
 115                 if(allUpper)    r=r.toUpperCase();
 116                 return r;
 117             }
 118         }
 119 
 120         // failed
 121         return word;
 122     }
 123 
 124 
 125     /** All reserved keywords of Java. */
 126     private static HashSet<String> reservedKeywords = new HashSet<String>();
 127 
 128     static {
 129         // see http://java.sun.com/docs/books/tutorial/java/nutsandbolts/_keywords.html
 130         String[] words = new String[]{
 131             "abstract",
 132             "boolean",
 133             "break",
 134             "byte",
 135             "case",
 136             "catch",
 137             "char",
 138             "class",
 139             "const",
 140             "continue",
 141             "default",
 142             "do",
 143             "double",
 144             "else",
 145             "extends",
 146             "final",
 147             "finally",
 148             "float",
 149             "for",
 150             "goto",
 151             "if",
 152             "implements",
 153             "import",
 154             "instanceof",
 155             "int",
 156             "interface",
 157             "long",
 158             "native",
 159             "new",
 160             "package",
 161             "private",
 162             "protected",
 163             "public",
 164             "return",
 165             "short",
 166             "static",
 167             "strictfp",
 168             "super",
 169             "switch",
 170             "synchronized",
 171             "this",
 172             "throw",
 173             "throws",
 174             "transient",
 175             "try",
 176             "void",
 177             "volatile",
 178             "while",
 179 
 180             // technically these are not reserved words but they cannot be used as identifiers.
 181             "true",
 182             "false",
 183             "null",
 184 
 185             // and I believe assert is also a new keyword
 186             "assert",
 187 
 188             // and 5.0 keywords
 189             "enum"
 190             };
 191         for (String w : words)
 192             reservedKeywords.add(w);
 193     }
 194 
 195 
 196     private static class Entry {
 197         private final Pattern pattern;
 198         private final String replacement;
 199 
 200         public Entry(String pattern, String replacement) {
 201             this.pattern = Pattern.compile(pattern,Pattern.CASE_INSENSITIVE);
 202             this.replacement = replacement;
 203         }
 204 
 205         String apply(String word) {
 206             Matcher m = pattern.matcher(word);
 207             if(m.matches()) {
 208                 StringBuffer buf = new StringBuffer();
 209                 m.appendReplacement(buf,replacement);
 210                 return buf.toString();
 211             } else {
 212                 return null;
 213             }
 214         }
 215     }
 216 
 217     private static final Entry[] TABLE;
 218 
 219     static {
 220         String[] source = {
 221               "(.*)child","$1children",
 222                  "(.+)fe","$1ves",
 223               "(.*)mouse","$1mise",
 224                   "(.+)f","$1ves",
 225                  "(.+)ch","$1ches",
 226                  "(.+)sh","$1shes",
 227               "(.*)tooth","$1teeth",
 228                  "(.+)um","$1a",
 229                  "(.+)an","$1en",
 230                 "(.+)ato","$1atoes",
 231               "(.*)basis","$1bases",
 232                "(.*)axis","$1axes",
 233                  "(.+)is","$1ises",
 234                  "(.+)ss","$1sses",
 235                  "(.+)us","$1uses",
 236                   "(.+)s","$1s",
 237                "(.*)foot","$1feet",
 238                  "(.+)ix","$1ixes",
 239                  "(.+)ex","$1ices",
 240                  "(.+)nx","$1nxes",
 241                   "(.+)x","$1xes",
 242                   "(.+)y","$1ies",
 243                    "(.+)","$1s",
 244         };
 245 
 246         TABLE = new Entry[source.length/2];
 247 
 248         for( int i=0; i<source.length; i+=2 ) {
 249             TABLE[i/2] = new Entry(source[i],source[i+1]);
 250         }
 251     }
 252 }