1 /*
   2  * Copyright (c) 1997, 2013, 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 }