IDLTypesUtil.java revision 608:7e06bf1dcb09
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
27package com.sun.corba.se.impl.presentation.rmi ;
28
29import java.lang.reflect.Method;
30import java.lang.reflect.Field;
31import java.util.Set;
32import java.util.HashSet;
33import 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 */
41public 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}
599