1 /*
   2  * Copyright (c) 2003, 2006, 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 
  27 package com.sun.corba.se.impl.presentation.rmi ;
  28 
  29 import java.lang.reflect.Method;
  30 import java.lang.reflect.Field;
  31 import java.util.Set;
  32 import java.util.HashSet;
  33 import java.util.Iterator;
  34 
  35 /**
  36  * Utility class for testing RMI/IDL Types as defined in
  37  * Section 1.2 of The Java Language to IDL Mapping.  Note that
  38  * these are static checks only.  Runtime checks, such as those
  39  * described in Section 1.2.3, #3, are not covered.
  40  */
  41 public final class IDLTypesUtil {
  42 
  43     private static final String GET_PROPERTY_PREFIX = "get";
  44     private static final String SET_PROPERTY_PREFIX = "set";
  45     private static final String IS_PROPERTY_PREFIX  = "is";
  46 
  47     public static final int VALID_TYPE   = 0;
  48     public static final int INVALID_TYPE = 1;
  49 
  50     /* rmic -iiop does not correctly implement the clause in 1.3.4.3
  51      * about is<NAME>/get<NAME> conflicts.  The spec says that
  52      * is<NAME> is the property and get<NAME> is left alone,
  53      * but rmic does the opposite.  We will follow rmic in this,
  54      * but it's easy to change.
  55      */
  56     public static final boolean FOLLOW_RMIC = true ;
  57 
  58     /**
  59      * Validate a class to ensure it conforms to the rules for a
  60      * Java RMI/IIOP interface.
  61      *
  62      * @throws IDLTypeException if not a valid RMI/IIOP interface.
  63      */
  64     public void validateRemoteInterface(Class c) throws IDLTypeException
  65     {
  66         if( c == null ) {
  67             throw new IllegalArgumentException();
  68         }
  69 
  70         if( !c.isInterface() ) {
  71             String msg = "Class " + c + " must be a java interface.";
  72             throw new IDLTypeException(msg);
  73         }
  74 
  75         if( !java.rmi.Remote.class.isAssignableFrom(c) ) {
  76             String msg = "Class " + c + " must extend java.rmi.Remote, " +
  77                 "either directly or indirectly.";
  78             throw new IDLTypeException(msg);
  79         }
  80 
  81         // Get all methods, including super-interface methods.
  82         Method[] methods = c.getMethods();
  83 
  84         for(int i = 0; i < methods.length; i++) {
  85             Method next = methods[i];
  86             validateExceptions(next);
  87         }
  88 
  89         // Removed because of bug 4989053
  90         // validateDirectInterfaces(c);
  91         validateConstants(c);
  92 
  93         return;
  94     }
  95 
  96     public boolean isRemoteInterface(Class c)
  97     {
  98         boolean remoteInterface = true;
  99         try {
 100             validateRemoteInterface(c);
 101         } catch(IDLTypeException ite) {
 102             remoteInterface = false;
 103         }
 104 
 105         return remoteInterface;
 106     }
 107 
 108     /**
 109      * Section 1.2.2 Primitive Types
 110      */
 111     public boolean isPrimitive(Class c)
 112     {
 113         if( c == null ) {
 114             throw new IllegalArgumentException();
 115         }
 116 
 117         return c.isPrimitive();
 118     }
 119 
 120     /**
 121      * Section 1.2.4
 122      */
 123     public boolean isValue(Class c)
 124     {
 125         if( c == null ) {
 126             throw new IllegalArgumentException();
 127         }
 128 
 129         return
 130             (!c.isInterface() &&
 131              java.io.Serializable.class.isAssignableFrom(c) &&
 132              !java.rmi.Remote.class.isAssignableFrom(c));
 133     }
 134 
 135     /**
 136      * Section 1.2.5
 137      */
 138     public boolean isArray(Class c)
 139     {
 140         boolean arrayType = false;
 141 
 142         if( c == null ) {
 143             throw new IllegalArgumentException();
 144         }
 145 
 146         if( c.isArray() ) {
 147             Class componentType = c.getComponentType();
 148             arrayType =
 149                 (isPrimitive(componentType) || isRemoteInterface(componentType) ||
 150                  isEntity(componentType) || isException(componentType) ||
 151                  isValue(componentType) || isObjectReference(componentType) );
 152         }
 153 
 154         return arrayType;
 155     }
 156 
 157     /**
 158      * Section 1.2.6
 159      */
 160     public boolean isException(Class c)
 161     {
 162         if( c == null ) {
 163             throw new IllegalArgumentException();
 164         }
 165 
 166         // Must be a checked exception, not including RemoteException or
 167         // its subclasses.
 168         return isCheckedException(c) && !isRemoteException(c) && isValue(c);
 169     }
 170 
 171     public boolean isRemoteException(Class c)
 172     {
 173         if( c == null ) {
 174             throw new IllegalArgumentException();
 175         }
 176 
 177         return java.rmi.RemoteException.class.isAssignableFrom(c) ;
 178     }
 179 
 180     public boolean isCheckedException(Class c)
 181     {
 182         if( c == null ) {
 183             throw new IllegalArgumentException();
 184         }
 185 
 186         return Throwable.class.isAssignableFrom(c) &&
 187             !RuntimeException.class.isAssignableFrom(c) &&
 188             !Error.class.isAssignableFrom(c) ;
 189     }
 190 
 191     /**
 192      * Section 1.2.7
 193      */
 194     public boolean isObjectReference(Class c)
 195     {
 196         if( c == null ) {
 197             throw new IllegalArgumentException();
 198         }
 199 
 200         return (c.isInterface() &&
 201                 org.omg.CORBA.Object.class.isAssignableFrom(c));
 202     }
 203 
 204     /**
 205      * Section 1.2.8
 206      */
 207     public boolean isEntity(Class c)
 208     {
 209         if( c == null ) {
 210             throw new IllegalArgumentException();
 211         }
 212 
 213         Class superClass = c.getSuperclass();
 214         return (!c.isInterface() &&
 215                 (superClass != null) &&
 216                 (org.omg.CORBA.portable.IDLEntity.class.isAssignableFrom(c)));
 217     }
 218 
 219     /**
 220      * Return true if given method is legal property accessor as defined in
 221      * Section 1.3.4.3 of Java2IDL spec.
 222      */
 223     public boolean isPropertyAccessorMethod(Method m, Class c) {
 224 
 225         String methodName = m.getName();
 226         Class returnType  = m.getReturnType();
 227         Class[] parameters = m.getParameterTypes();
 228         Class[] exceptionTypes = m.getExceptionTypes();
 229         String propertyType = null;
 230 
 231         if( methodName.startsWith(GET_PROPERTY_PREFIX) ) {
 232 
 233             if((parameters.length == 0) && (returnType != Void.TYPE) &&
 234                 !readHasCorrespondingIsProperty(m, c)) {
 235                 propertyType = GET_PROPERTY_PREFIX;
 236             }
 237 
 238         } else if( methodName.startsWith(SET_PROPERTY_PREFIX) ) {
 239 
 240             if((returnType == Void.TYPE) && (parameters.length == 1)) {
 241                 if (hasCorrespondingReadProperty(m, c, GET_PROPERTY_PREFIX) ||
 242                     hasCorrespondingReadProperty(m, c, IS_PROPERTY_PREFIX)) {
 243                     propertyType = SET_PROPERTY_PREFIX;
 244                 }
 245             }
 246 
 247         } else if( methodName.startsWith(IS_PROPERTY_PREFIX) ) {
 248             if((parameters.length == 0) && (returnType == Boolean.TYPE) &&
 249                 !isHasCorrespondingReadProperty(m, c)) {
 250                 propertyType = IS_PROPERTY_PREFIX;
 251             }
 252         }
 253 
 254         // Some final checks that apply to all properties.
 255         if( propertyType != null ) {
 256             if(!validPropertyExceptions(m) ||
 257                (methodName.length() <= propertyType.length())) {
 258                 propertyType = null;
 259             }
 260         }
 261 
 262         return (propertyType != null);
 263     }
 264 
 265     private boolean hasCorrespondingReadProperty
 266         (Method writeProperty, Class c, String readPropertyPrefix)
 267     {
 268         String writePropertyMethodName = writeProperty.getName();
 269         Class[] writePropertyParameters = writeProperty.getParameterTypes();
 270         boolean foundReadProperty = false;
 271 
 272         try {
 273             // Look for a valid corresponding Read property
 274             String readPropertyMethodName =
 275                 writePropertyMethodName.replaceFirst
 276                     (SET_PROPERTY_PREFIX, readPropertyPrefix);
 277             Method readPropertyMethod = c.getMethod(readPropertyMethodName,
 278                                                     new Class[] {});
 279             foundReadProperty =
 280                 ( isPropertyAccessorMethod(readPropertyMethod, c) &&
 281                   (readPropertyMethod.getReturnType() ==
 282                    writePropertyParameters[0]) );
 283         } catch(Exception e) {
 284             // ignore. this means we didn't find a corresponding get property.
 285         }
 286 
 287         return foundReadProperty;
 288     }
 289 
 290     private boolean readHasCorrespondingIsProperty(Method readProperty,
 291         Class c)
 292     {
 293         if (FOLLOW_RMIC)
 294             return false ;
 295 
 296         String readPropertyMethodName = readProperty.getName();
 297         boolean foundIsProperty = false;
 298 
 299         try {
 300             // Look for a valid corresponding Is property
 301             String isPropertyMethodName =
 302                 readPropertyMethodName.replaceFirst(GET_PROPERTY_PREFIX,
 303                     IS_PROPERTY_PREFIX);
 304             Method isPropertyMethod = c.getMethod( isPropertyMethodName,
 305                                                     new Class[] {});
 306             foundIsProperty = isPropertyAccessorMethod(isPropertyMethod,
 307                 c) ;
 308         } catch(Exception e) {
 309             // ignore. this means we didn't find a corresponding Is property.
 310         }
 311 
 312         return foundIsProperty;
 313     }
 314 
 315     private boolean isHasCorrespondingReadProperty(Method readProperty,
 316         Class c)
 317     {
 318         if (!FOLLOW_RMIC)
 319             return false ;
 320 
 321         String readPropertyMethodName = readProperty.getName();
 322         boolean foundIsProperty = false;
 323 
 324         try {
 325             // Look for a valid corresponding Read property
 326             String isPropertyMethodName =
 327                 readPropertyMethodName.replaceFirst(IS_PROPERTY_PREFIX,
 328                     GET_PROPERTY_PREFIX);
 329             Method isPropertyMethod = c.getMethod( isPropertyMethodName,
 330                                                     new Class[] {});
 331             foundIsProperty = isPropertyAccessorMethod(isPropertyMethod,
 332                 c) ;
 333         } catch(Exception e) {
 334             // ignore. this means we didn't find a corresponding read property.
 335         }
 336 
 337         return foundIsProperty;
 338     }
 339 
 340     public String getAttributeNameForProperty(String propertyName) {
 341         String attributeName = null;
 342         String prefix = null;
 343 
 344         if( propertyName.startsWith(GET_PROPERTY_PREFIX) ) {
 345             prefix = GET_PROPERTY_PREFIX;
 346         } else if( propertyName.startsWith(SET_PROPERTY_PREFIX) ) {
 347             prefix = SET_PROPERTY_PREFIX;
 348         } else if( propertyName.startsWith(IS_PROPERTY_PREFIX) ) {
 349             prefix = IS_PROPERTY_PREFIX;
 350         }
 351 
 352         if( (prefix != null) && (prefix.length() < propertyName.length()) ) {
 353             String remainder = propertyName.substring(prefix.length());
 354             if( (remainder.length() >= 2) &&
 355                 Character.isUpperCase(remainder.charAt(0)) &&
 356                 Character.isUpperCase(remainder.charAt(1)) ) {
 357                 // don't set the first letter to lower-case if the
 358                 // first two are upper-case
 359                 attributeName = remainder;
 360             } else {
 361                 attributeName = Character.toLowerCase(remainder.charAt(0)) +
 362                     remainder.substring(1);
 363             }
 364         }
 365 
 366         return attributeName;
 367     }
 368 
 369     /**
 370      * Return IDL Type name for primitive types as defined in
 371      * Section 1.3.3 of Java2IDL spec or null if not a primitive type.
 372      */
 373     public IDLType getPrimitiveIDLTypeMapping(Class c) {
 374 
 375         if( c == null ) {
 376             throw new IllegalArgumentException();
 377         }
 378 
 379         if( c.isPrimitive() ) {
 380             if( c == Void.TYPE ) {
 381                 return new IDLType( c, "void" ) ;
 382             } else if( c == Boolean.TYPE ) {
 383                 return new IDLType( c, "boolean" ) ;
 384             } else if( c == Character.TYPE ) {
 385                 return new IDLType( c, "wchar" ) ;
 386             } else if( c == Byte.TYPE ) {
 387                 return new IDLType( c, "octet" ) ;
 388             } else if( c == Short.TYPE ) {
 389                 return new IDLType( c, "short" ) ;
 390             } else if( c == Integer.TYPE ) {
 391                 return new IDLType( c, "long" ) ;
 392             } else if( c == Long.TYPE ) {
 393                 return new IDLType( c, "long_long" ) ;
 394             } else if( c == Float.TYPE ) {
 395                 return new IDLType( c, "float" ) ;
 396             } else if( c == Double.TYPE ) {
 397                 return new IDLType( c, "double" ) ;
 398             }
 399         }
 400 
 401         return null;
 402     }
 403 
 404     /**
 405      * Return IDL Type name for special case type mappings as defined in
 406      * Table 1-1 of Java2IDL spec or null if given class is not a special
 407      * type.
 408      */
 409     public IDLType getSpecialCaseIDLTypeMapping(Class c) {
 410 
 411         if( c == null ) {
 412             throw new IllegalArgumentException();
 413         }
 414 
 415         if( c == java.lang.Object.class ) {
 416             return new IDLType( c, new String[] { "java", "lang" },
 417                 "Object" ) ;
 418         } else if( c == java.lang.String.class ) {
 419             return new IDLType( c, new String[] { "CORBA" },
 420                 "WStringValue" ) ;
 421         } else if( c == java.lang.Class.class ) {
 422             return new IDLType( c, new String[] { "javax", "rmi", "CORBA" },
 423                 "ClassDesc" ) ;
 424         } else if( c == java.io.Serializable.class ) {
 425             return new IDLType( c, new String[] { "java", "io" },
 426                 "Serializable" ) ;
 427         } else if( c == java.io.Externalizable.class ) {
 428             return new IDLType( c, new String[] { "java", "io" },
 429                 "Externalizable" ) ;
 430         } else if( c == java.rmi.Remote.class ) {
 431             return new IDLType( c, new String[] { "java", "rmi" },
 432                 "Remote" ) ;
 433         } else if( c == org.omg.CORBA.Object.class ) {
 434             return new IDLType( c, "Object" ) ;
 435         } else {
 436             return null;
 437         }
 438     }
 439 
 440     /**
 441      * Implements 1.2.3 #2 and #4
 442      */
 443     private void validateExceptions(Method method) throws IDLTypeException {
 444 
 445         Class[] exceptions = method.getExceptionTypes();
 446 
 447         boolean declaresRemoteExceptionOrSuperClass = false;
 448 
 449         // Section 1.2.3, #2
 450         for(int eIndex = 0; eIndex < exceptions.length; eIndex++) {
 451             Class exception = exceptions[eIndex];
 452             if( isRemoteExceptionOrSuperClass(exception) ) {
 453                 declaresRemoteExceptionOrSuperClass = true;
 454                 break;
 455             }
 456         }
 457 
 458         if( !declaresRemoteExceptionOrSuperClass ) {
 459             String msg = "Method '" + method + "' must throw at least one " +
 460                 "exception of type java.rmi.RemoteException or one of its " +
 461                 "super-classes";
 462             throw new IDLTypeException(msg);
 463         }
 464 
 465         // Section 1.2.3, #4
 466         // See also bug 4972402
 467         // For all exceptions E in exceptions,
 468         // (isCheckedException(E) => (isValue(E) || RemoteException.isAssignableFrom( E ) )
 469         for(int eIndex = 0; eIndex < exceptions.length; eIndex++) {
 470             Class exception = exceptions[eIndex];
 471 
 472             if (isCheckedException(exception) && !isValue(exception) &&
 473                 !isRemoteException(exception))
 474             {
 475                 String msg = "Exception '" + exception + "' on method '" +
 476                     method + "' is not a allowed RMI/IIOP exception type";
 477                 throw new IDLTypeException(msg);
 478             }
 479         }
 480 
 481         return;
 482     }
 483 
 484     /**
 485      * Returns true if the method's throw clause conforms to the exception
 486      * restrictions for properties as defined in Section 1.3.4.3 of
 487      * Java2IDL spec.  This means that for all exceptions E declared on the
 488      * method, E isChecked => RemoteException.isAssignableFrom( E ).
 489      */
 490     private boolean validPropertyExceptions(Method method)
 491     {
 492         Class[] exceptions = method.getExceptionTypes();
 493 
 494         for(int eIndex = 0; eIndex < exceptions.length; eIndex++) {
 495             Class exception = exceptions[eIndex];
 496 
 497             if (isCheckedException(exception) && !isRemoteException(exception))
 498                 return false ;
 499         }
 500 
 501         return true;
 502     }
 503 
 504     /**
 505      * Implements Section 1.2.3, #2.
 506      */
 507     private boolean isRemoteExceptionOrSuperClass(Class c) {
 508         return
 509             ((c == java.rmi.RemoteException.class) ||
 510              (c == java.io.IOException.class) ||
 511              (c == java.lang.Exception.class) ||
 512              (c == java.lang.Throwable.class));
 513     }
 514 
 515     /**
 516      * Implements Section 1.2.3, #5.
 517      */
 518     private void validateDirectInterfaces(Class c) throws IDLTypeException {
 519 
 520         Class[] directInterfaces = c.getInterfaces();
 521 
 522         if( directInterfaces.length < 2 ) {
 523             return;
 524         }
 525 
 526         Set allMethodNames = new HashSet();
 527         Set currentMethodNames = new HashSet();
 528 
 529         for(int i = 0; i < directInterfaces.length; i++) {
 530             Class next = directInterfaces[i];
 531             Method[] methods = next.getMethods();
 532 
 533             // Comparison is based on method names only.  First collect
 534             // all methods from current interface, eliminating duplicate
 535             // names.
 536             currentMethodNames.clear();
 537             for(int m = 0; m < methods.length; m++) {
 538                 currentMethodNames.add(methods[m].getName());
 539             }
 540 
 541             // Now check each method against list of all unique method
 542             // names processed so far.
 543             for(Iterator iter=currentMethodNames.iterator(); iter.hasNext();) {
 544                 String methodName = (String) iter.next();
 545                 if( allMethodNames.contains(methodName) ) {
 546                     String msg = "Class " + c + " inherits method " +
 547                         methodName + " from multiple direct interfaces.";
 548                     throw new IDLTypeException(msg);
 549                 } else {
 550                     allMethodNames.add(methodName);
 551                 }
 552             }
 553         }
 554 
 555         return;
 556     }
 557 
 558     /**
 559      * Implements 1.2.3 #6
 560      */
 561     private void validateConstants(final Class c)
 562         throws IDLTypeException {
 563 
 564         Field[] fields = null;
 565 
 566         try {
 567             fields = (Field[])
 568                 java.security.AccessController.doPrivileged
 569                 (new java.security.PrivilegedExceptionAction() {
 570                         public java.lang.Object run() throws Exception {
 571                             return c.getFields();
 572                         }
 573                     });
 574         } catch(java.security.PrivilegedActionException pae) {
 575             IDLTypeException ite = new IDLTypeException();
 576             ite.initCause(pae);
 577             throw ite;
 578         }
 579 
 580         for(int i = 0; i < fields.length; i++) {
 581             Field next = fields[i];
 582             Class fieldType = next.getType();
 583             if( (fieldType != java.lang.String.class) &&
 584                 !isPrimitive(fieldType) ) {
 585                 String msg = "Constant field '" + next.getName() +
 586                     "' in class '" + next.getDeclaringClass().getName() +
 587                     "' has invalid type' " + next.getType() + "'. Constants" +
 588                     " in RMI/IIOP interfaces can only have primitive" +
 589                     " types and java.lang.String types.";
 590                 throw new IDLTypeException(msg);
 591             }
 592         }
 593 
 594 
 595         return;
 596     }
 597 
 598 }