View Javadoc
1   /*
2    * Copyright (c) 1997, 2010, 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.xml.internal.bind.api.impl;
27  
28  import javax.lang.model.SourceVersion;
29  import java.util.ArrayList;
30  import java.util.List;
31  import java.util.StringTokenizer;
32  
33  /**
34   * Converts aribitrary strings into Java identifiers.
35   *
36   * @author
37   *    <a href="mailto:kohsuke.kawaguchi@sun.com">Kohsuke KAWAGUCHI</a>
38   */
39  public interface NameConverter
40  {
41      /**
42       * converts a string into an identifier suitable for classes.
43       *
44       * In general, this operation should generate "NamesLikeThis".
45       */
46      String toClassName( String token );
47  
48      /**
49       * converts a string into an identifier suitable for interfaces.
50       *
51       * In general, this operation should generate "NamesLikeThis".
52       * But for example, it can prepend every interface with 'I'.
53       */
54      String toInterfaceName( String token );
55  
56      /**
57       * converts a string into an identifier suitable for properties.
58       *
59       * In general, this operation should generate "NamesLikeThis",
60       * which will be used with known prefixes like "get" or "set".
61       */
62      String toPropertyName( String token );
63  
64      /**
65       * converts a string into an identifier suitable for constants.
66       *
67       * In the standard Java naming convention, this operation should
68       * generate "NAMES_LIKE_THIS".
69       */
70      String toConstantName( String token );
71  
72      /**
73       * Converts a string into an identifier suitable for variables.
74       *
75       * In general it should generate "namesLikeThis".
76       */
77      String toVariableName( String token );
78  
79      /**
80       * Converts a namespace URI into a package name.
81       * This method should expect strings like
82       * "http://foo.bar.zot/org", "urn:abc:def:ghi" "", or even "###"
83       * (basically anything) and expected to return a package name,
84       * liks "org.acme.foo".
85       *
86       */
87      String toPackageName( String namespaceUri );
88  
89      /**
90       * The name converter implemented by Code Model.
91       *
92       * This is the standard name conversion for JAXB.
93       */
94      public static final NameConverter standard = new Standard();
95  
96      static class Standard extends NameUtil implements NameConverter {
97          public String toClassName(String s) {
98              return toMixedCaseName(toWordList(s), true);
99          }
100         public String toVariableName(String s) {
101             return toMixedCaseName(toWordList(s), false);
102         }
103         public String toInterfaceName( String token ) {
104             return toClassName(token);
105         }
106         public String toPropertyName(String s) {
107             String prop = toClassName(s);
108             // property name "Class" with collide with Object.getClass,
109             // so escape this.
110             if(prop.equals("Class"))
111                 prop = "Clazz";
112             return prop;
113         }
114         public String toConstantName( String token ) {
115             return super.toConstantName(token);
116         }
117         /**
118          * Computes a Java package name from a namespace URI,
119          * as specified in the spec.
120          *
121          * @return
122          *      null if it fails to derive a package name.
123          */
124         public String toPackageName( String nsUri ) {
125             // remove scheme and :, if present
126             // spec only requires us to remove 'http' and 'urn'...
127             int idx = nsUri.indexOf(':');
128             String scheme = "";
129             if(idx>=0) {
130                 scheme = nsUri.substring(0,idx);
131                 if( scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("urn") )
132                     nsUri = nsUri.substring(idx+1);
133             }
134 
135             // tokenize string
136             ArrayList<String> tokens = tokenize( nsUri, "/: " );
137             if( tokens.size() == 0 ) {
138                 return null;
139             }
140 
141             // remove trailing file type, if necessary
142             if( tokens.size() > 1 ) {
143                 // for uri's like "www.foo.com" and "foo.com", there is no trailing
144                 // file, so there's no need to look at the last '.' and substring
145                 // otherwise, we loose the "com" (which would be wrong)
146                 String lastToken = tokens.get( tokens.size()-1 );
147                 idx = lastToken.lastIndexOf( '.' );
148                 if( idx > 0 ) {
149                     lastToken = lastToken.substring( 0, idx );
150                     tokens.set( tokens.size()-1, lastToken );
151                 }
152             }
153 
154             // tokenize domain name and reverse.  Also remove :port if it exists
155             String domain = tokens.get( 0 );
156             idx = domain.indexOf(':');
157             if( idx >= 0) domain = domain.substring(0, idx);
158             ArrayList<String> r = reverse( tokenize( domain, scheme.equals("urn")?".-":"." ) );
159             if( r.get( r.size()-1 ).equalsIgnoreCase( "www" ) ) {
160                 // remove leading www
161                 r.remove( r.size()-1 );
162             }
163 
164             // replace the domain name with tokenized items
165             tokens.addAll( 1, r );
166             tokens.remove( 0 );
167 
168             // iterate through the tokens and apply xml->java name algorithm
169             for( int i = 0; i < tokens.size(); i++ ) {
170 
171                 // get the token and remove illegal chars
172                 String token = tokens.get( i );
173                 token = removeIllegalIdentifierChars( token );
174 
175                 // this will check for reserved keywords
176                 if (SourceVersion.isKeyword(token.toLowerCase())) {
177                     token = '_' + token;
178                 }
179 
180                 tokens.set( i, token.toLowerCase() );
181             }
182 
183             // concat all the pieces and return it
184             return combine( tokens, '.' );
185         }
186 
187 
188         private static String removeIllegalIdentifierChars(String token) {
189             StringBuilder newToken = new StringBuilder(token.length() + 1); // max expected length
190             for( int i = 0; i < token.length(); i++ ) {
191                 char c = token.charAt( i );
192                 if (i == 0 && !Character.isJavaIdentifierStart(c)) { // c can't be used as FIRST char
193                     newToken.append('_');
194                 }
195                 if (!Character.isJavaIdentifierPart(c)) { // c can't be used
196                     newToken.append('_');
197                 } else {
198                     newToken.append(c); // c is valid
199                 }
200             }
201             return newToken.toString();
202         }
203 
204 
205         private static ArrayList<String> tokenize( String str, String sep ) {
206             StringTokenizer tokens = new StringTokenizer(str,sep);
207             ArrayList<String> r = new ArrayList<String>();
208 
209             while(tokens.hasMoreTokens())
210                 r.add( tokens.nextToken() );
211 
212             return r;
213         }
214 
215         private static <T> ArrayList<T> reverse( List<T> a ) {
216             ArrayList<T> r = new ArrayList<T>();
217 
218             for( int i=a.size()-1; i>=0; i-- )
219                 r.add( a.get(i) );
220 
221             return r;
222         }
223 
224         private static String combine( List r, char sep ) {
225             StringBuilder buf = new StringBuilder(r.get(0).toString());
226 
227             for( int i=1; i<r.size(); i++ ) {
228                 buf.append(sep);
229                 buf.append(r.get(i));
230             }
231 
232             return buf.toString();
233         }
234     }
235 
236     /**
237      * JAX-PRC compatible name converter implementation.
238      *
239      * The only difference is that we treat '_' as a valid character
240      * and not as a word separator.
241      */
242     public static final NameConverter jaxrpcCompatible = new Standard() {
243         protected boolean isPunct(char c) {
244             return (c == '.' || c == '-' || c == ';' /*|| c == '_'*/ || c == '\u00b7'
245                     || c == '\u0387' || c == '\u06dd' || c == '\u06de');
246         }
247         protected boolean isLetter(char c) {
248             return super.isLetter(c) || c=='_';
249         }
250 
251         protected int classify(char c0) {
252             if(c0=='_') return NameUtil.OTHER_LETTER;
253             return super.classify(c0);
254         }
255     };
256 
257     /**
258      * Smarter converter used for RELAX NG support.
259      */
260     public static final NameConverter smart = new Standard() {
261         public String toConstantName( String token ) {
262             String name = super.toConstantName(token);
263             if(!SourceVersion.isKeyword(name))
264                 return name;
265             else
266                 return '_'+name;
267         }
268     };
269 }