1/*
2 * Copyright (c) 1997, 2017, 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
26package com.sun.codemodel.internal;
27
28import java.util.HashSet;
29import java.util.regex.Matcher;
30import java.util.regex.Pattern;
31
32/**
33 * Utility methods that convert arbitrary strings into Java identifiers.
34 */
35public 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, "{@literal &#x65E5;&#x672C;&#x8A9E;}"
89     *      will be returned as-is without modified, not "{@literal &#x65E5;&#x672C;&#x8A9E;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}
253