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