blob: f22a652dfdb5c7390b6dc9bd23efd66cd2191767 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2003 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.rmi.rmic.newrmic.jrmp;
27
28import com.sun.javadoc.ClassDoc;
29import com.sun.javadoc.MethodDoc;
30import com.sun.javadoc.Parameter;
31import com.sun.javadoc.Type;
32import java.io.IOException;
33import java.io.ByteArrayOutputStream;
34import java.io.DataOutputStream;
35import java.security.MessageDigest;
36import java.security.DigestOutputStream;
37import java.security.NoSuchAlgorithmException;
38import java.util.ArrayList;
39import java.util.Arrays;
40import java.util.Comparator;
41import java.util.List;
42import java.util.HashMap;
43import java.util.Map;
44import sun.rmi.rmic.newrmic.BatchEnvironment;
45
46import static sun.rmi.rmic.newrmic.Constants.*;
47import static sun.rmi.rmic.newrmic.jrmp.Constants.*;
48
49/**
50 * Encapsulates RMI-specific information about a remote implementation
51 * class (a class that implements one or more remote interfaces).
52 *
53 * WARNING: The contents of this source file are not part of any
54 * supported API. Code that depends on them does so at its own risk:
55 * they are subject to change or removal without notice.
56 *
57 * @author Peter Jones
58 **/
59final class RemoteClass {
60
61 /** rmic environment for this object */
62 private final BatchEnvironment env;
63
64 /** the remote implementation class this object represents */
65 private final ClassDoc implClass;
66
67 /** remote interfaces implemented by this class */
68 private ClassDoc[] remoteInterfaces;
69
70 /** the remote methods of this class */
71 private Method[] remoteMethods;
72
73 /** stub/skeleton "interface hash" for this class */
74 private long interfaceHash;
75
76 /**
77 * Creates a RemoteClass instance that represents the RMI-specific
78 * information about the specified remote implementation class.
79 *
80 * If the class is not a valid remote implementation class or if
81 * some other error occurs, the return value will be null, and
82 * errors will have been reported to the supplied
83 * BatchEnvironment.
84 **/
85 static RemoteClass forClass(BatchEnvironment env, ClassDoc implClass) {
86 RemoteClass remoteClass = new RemoteClass(env, implClass);
87 if (remoteClass.init()) {
88 return remoteClass;
89 } else {
90 return null;
91 }
92 }
93
94 /**
95 * Creates a RemoteClass instance for the specified class. The
96 * resulting object is not yet initialized.
97 **/
98 private RemoteClass(BatchEnvironment env, ClassDoc implClass) {
99 this.env = env;
100 this.implClass = implClass;
101 }
102
103 /**
104 * Returns the ClassDoc for this remote implementation class.
105 **/
106 ClassDoc classDoc() {
107 return implClass;
108 }
109
110 /**
111 * Returns the remote interfaces implemented by this remote
112 * implementation class.
113 *
114 * A remote interface is an interface that is a subinterface of
115 * java.rmi.Remote. The remote interfaces of a class are the
116 * direct superinterfaces of the class and all of its superclasses
117 * that are remote interfaces.
118 *
119 * The order of the array returned is arbitrary, and some elements
120 * may be superfluous (i.e., superinterfaces of other interfaces
121 * in the array).
122 **/
123 ClassDoc[] remoteInterfaces() {
124 return (ClassDoc[]) remoteInterfaces.clone();
125 }
126
127 /**
128 * Returns an array of RemoteClass.Method objects representing all
129 * of the remote methods of this remote implementation class (all
130 * of the member methods of the class's remote interfaces).
131 *
132 * The methods in the array are ordered according to a comparison
133 * of strings consisting of their name followed by their
134 * descriptor, so each method's index in the array corresponds to
135 * its "operation number" in the JDK 1.1 version of the JRMP
136 * stub/skeleton protocol.
137 **/
138 Method[] remoteMethods() {
139 return (Method[]) remoteMethods.clone();
140 }
141
142 /**
143 * Returns the "interface hash" used to match a stub/skeleton pair
144 * for this remote implementation class in the JDK 1.1 version of
145 * the JRMP stub/skeleton protocol.
146 **/
147 long interfaceHash() {
148 return interfaceHash;
149 }
150
151 /**
152 * Validates this remote implementation class and computes the
153 * RMI-specific information. Returns true if successful, or false
154 * if an error occurred.
155 **/
156 private boolean init() {
157 /*
158 * Verify that it is really a class, not an interface.
159 */
160 if (implClass.isInterface()) {
161 env.error("rmic.cant.make.stubs.for.interface",
162 implClass.qualifiedName());
163 return false;
164 }
165
166 /*
167 * Find all of the remote interfaces of our remote
168 * implementation class-- for each class up the superclass
169 * chain, add each directly-implemented interface that somehow
170 * extends Remote to a list.
171 */
172 List<ClassDoc> remotesImplemented = new ArrayList<ClassDoc>();
173 for (ClassDoc cl = implClass; cl != null; cl = cl.superclass()) {
174 for (ClassDoc intf : cl.interfaces()) {
175 /*
176 * Add interface to the list if it extends Remote and
177 * it is not already there.
178 */
179 if (!remotesImplemented.contains(intf) &&
180 intf.subclassOf(env.docRemote()))
181 {
182 remotesImplemented.add(intf);
183 if (env.verbose()) {
184 env.output("[found remote interface: " +
185 intf.qualifiedName() + "]");
186 }
187 }
188 }
189
190 /*
191 * Verify that the candidate remote implementation class
192 * implements at least one remote interface directly.
193 */
194 if (cl == implClass && remotesImplemented.isEmpty()) {
195 if (implClass.subclassOf(env.docRemote())) {
196 /*
197 * This error message is used if the class does
198 * implement a remote interface through one of its
199 * superclasses, but not directly.
200 */
201 env.error("rmic.must.implement.remote.directly",
202 implClass.qualifiedName());
203 } else {
204 /*
205 * This error message is used if the class does
206 * not implement a remote interface at all.
207 */
208 env.error("rmic.must.implement.remote",
209 implClass.qualifiedName());
210 }
211 return false;
212 }
213 }
214
215 /*
216 * Convert list of remote interfaces to an array
217 * (order is not important for this array).
218 */
219 remoteInterfaces =
220 remotesImplemented.toArray(
221 new ClassDoc[remotesImplemented.size()]);
222
223 /*
224 * Collect the methods from all of the remote interfaces into
225 * a table, which maps from method name-and-descriptor string
226 * to Method object.
227 */
228 Map<String,Method> methods = new HashMap<String,Method>();
229 boolean errors = false;
230 for (ClassDoc intf : remotesImplemented) {
231 if (!collectRemoteMethods(intf, methods)) {
232 /*
233 * Continue iterating despite errors in order to
234 * generate more complete error report.
235 */
236 errors = true;
237 }
238 }
239 if (errors) {
240 return false;
241 }
242
243 /*
244 * Sort table of remote methods into an array. The elements
245 * are sorted in ascending order of the string of the method's
246 * name and descriptor, so that each elements index is equal
247 * to its operation number in the JDK 1.1 version of the JRMP
248 * stub/skeleton protocol.
249 */
250 String[] orderedKeys =
251 methods.keySet().toArray(new String[methods.size()]);
252 Arrays.sort(orderedKeys);
253 remoteMethods = new Method[methods.size()];
254 for (int i = 0; i < remoteMethods.length; i++) {
255 remoteMethods[i] = methods.get(orderedKeys[i]);
256 if (env.verbose()) {
257 String msg = "[found remote method <" + i + ">: " +
258 remoteMethods[i].operationString();
259 ClassDoc[] exceptions = remoteMethods[i].exceptionTypes();
260 if (exceptions.length > 0) {
261 msg += " throws ";
262 for (int j = 0; j < exceptions.length; j++) {
263 if (j > 0) {
264 msg += ", ";
265 }
266 msg += exceptions[j].qualifiedName();
267 }
268 }
269 msg += "\n\tname and descriptor = \"" +
270 remoteMethods[i].nameAndDescriptor();
271 msg += "\n\tmethod hash = " +
272 remoteMethods[i].methodHash() + "]";
273 env.output(msg);
274 }
275 }
276
277 /*
278 * Finally, pre-compute the interface hash to be used by
279 * stubs/skeletons for this remote class in the JDK 1.1
280 * version of the JRMP stub/skeleton protocol.
281 */
282 interfaceHash = computeInterfaceHash();
283
284 return true;
285 }
286
287 /**
288 * Collects and validates all methods from the specified interface
289 * and all of its superinterfaces as remote methods. Remote
290 * methods are added to the supplied table. Returns true if
291 * successful, or false if an error occurred.
292 **/
293 private boolean collectRemoteMethods(ClassDoc intf,
294 Map<String,Method> table)
295 {
296 if (!intf.isInterface()) {
297 throw new AssertionError(
298 intf.qualifiedName() + " not an interface");
299 }
300
301 boolean errors = false;
302
303 /*
304 * Search interface's declared methods.
305 */
306 nextMethod:
307 for (MethodDoc method : intf.methods()) {
308
309 /*
310 * Verify that each method throws RemoteException (or a
311 * superclass of RemoteException).
312 */
313 boolean hasRemoteException = false;
314 for (ClassDoc ex : method.thrownExceptions()) {
315 if (env.docRemoteException().subclassOf(ex)) {
316 hasRemoteException = true;
317 break;
318 }
319 }
320
321 /*
322 * If this method did not throw RemoteException as required,
323 * generate the error but continue, so that multiple such
324 * errors can be reported.
325 */
326 if (!hasRemoteException) {
327 env.error("rmic.must.throw.remoteexception",
328 intf.qualifiedName(),
329 method.name() + method.signature());
330 errors = true;
331 continue nextMethod;
332 }
333
334 /*
335 * Verify that the implementation of this method throws only
336 * java.lang.Exception or its subclasses (fix bugid 4092486).
337 * JRMP does not support remote methods throwing
338 * java.lang.Throwable or other subclasses.
339 */
340 MethodDoc implMethod = findImplMethod(method);
341 if (implMethod != null) { // should not be null
342 for (ClassDoc ex : implMethod.thrownExceptions()) {
343 if (!ex.subclassOf(env.docException())) {
344 env.error("rmic.must.only.throw.exception",
345 implMethod.name() + implMethod.signature(),
346 ex.qualifiedName());
347 errors = true;
348 continue nextMethod;
349 }
350 }
351 }
352
353 /*
354 * Create RemoteClass.Method object to represent this method
355 * found in a remote interface.
356 */
357 Method newMethod = new Method(method);
358
359 /*
360 * Store remote method's representation in the table of
361 * remote methods found, keyed by its name and descriptor.
362 *
363 * If the table already contains an entry with the same
364 * method name and descriptor, then we must replace the
365 * old entry with a Method object that represents a legal
366 * combination of the old and the new methods;
367 * specifically, the combined method must have a throws
368 * clause that contains (only) all of the checked
369 * exceptions that can be thrown by both the old and the
370 * new method (see bugid 4070653).
371 */
372 String key = newMethod.nameAndDescriptor();
373 Method oldMethod = table.get(key);
374 if (oldMethod != null) {
375 newMethod = newMethod.mergeWith(oldMethod);
376 }
377 table.put(key, newMethod);
378 }
379
380 /*
381 * Recursively collect methods for all superinterfaces.
382 */
383 for (ClassDoc superintf : intf.interfaces()) {
384 if (!collectRemoteMethods(superintf, table)) {
385 errors = true;
386 }
387 }
388
389 return !errors;
390 }
391
392 /**
393 * Returns the MethodDoc for the method of this remote
394 * implementation class that implements the specified remote
395 * method of a remote interface. Returns null if no matching
396 * method was found in this remote implementation class.
397 **/
398 private MethodDoc findImplMethod(MethodDoc interfaceMethod) {
399 String name = interfaceMethod.name();
400 String desc = Util.methodDescriptorOf(interfaceMethod);
401 for (MethodDoc implMethod : implClass.methods()) {
402 if (name.equals(implMethod.name()) &&
403 desc.equals(Util.methodDescriptorOf(implMethod)))
404 {
405 return implMethod;
406 }
407 }
408 return null;
409 }
410
411 /**
412 * Computes the "interface hash" of the stub/skeleton pair for
413 * this remote implementation class. This is the 64-bit value
414 * used to enforce compatibility between a stub class and a
415 * skeleton class in the JDK 1.1 version of the JRMP stub/skeleton
416 * protocol.
417 *
418 * It is calculated using the first 64 bits of an SHA digest. The
419 * digest is of a stream consisting of the following data:
420 * (int) stub version number, always 1
421 * for each remote method, in order of operation number:
422 * (UTF-8) method name
423 * (UTF-8) method descriptor
424 * for each declared exception, in alphabetical name order:
425 * (UTF-8) name of exception class
426 * (where "UTF-8" includes a 16-bit length prefix as written by
427 * java.io.DataOutput.writeUTF).
428 **/
429 private long computeInterfaceHash() {
430 long hash = 0;
431 ByteArrayOutputStream sink = new ByteArrayOutputStream(512);
432 try {
433 MessageDigest md = MessageDigest.getInstance("SHA");
434 DataOutputStream out = new DataOutputStream(
435 new DigestOutputStream(sink, md));
436
437 out.writeInt(INTERFACE_HASH_STUB_VERSION);
438
439 for (Method method : remoteMethods) {
440 MethodDoc methodDoc = method.methodDoc();
441
442 out.writeUTF(methodDoc.name());
443 out.writeUTF(Util.methodDescriptorOf(methodDoc));
444 // descriptors already use binary names
445
446 ClassDoc exceptions[] = methodDoc.thrownExceptions();
447 Arrays.sort(exceptions, new ClassDocComparator());
448 for (ClassDoc ex : exceptions) {
449 out.writeUTF(Util.binaryNameOf(ex));
450 }
451 }
452 out.flush();
453
454 // use only the first 64 bits of the digest for the hash
455 byte hashArray[] = md.digest();
456 for (int i = 0; i < Math.min(8, hashArray.length); i++) {
457 hash += ((long) (hashArray[i] & 0xFF)) << (i * 8);
458 }
459 } catch (IOException e) {
460 throw new AssertionError(e);
461 } catch (NoSuchAlgorithmException e) {
462 throw new AssertionError(e);
463 }
464
465 return hash;
466 }
467
468 /**
469 * Compares ClassDoc instances according to the lexicographic
470 * order of their binary names.
471 **/
472 private static class ClassDocComparator implements Comparator<ClassDoc> {
473 public int compare(ClassDoc o1, ClassDoc o2) {
474 return Util.binaryNameOf(o1).compareTo(Util.binaryNameOf(o2));
475 }
476 }
477
478 /**
479 * Encapsulates RMI-specific information about a particular remote
480 * method in the remote implementation class represented by the
481 * enclosing RemoteClass.
482 **/
483 final class Method implements Cloneable {
484
485 /**
486 * MethodDoc for this remove method, from one of the remote
487 * interfaces that this method was found in.
488 *
489 * Note that this MethodDoc may be only one of multiple that
490 * correspond to this remote method object, if multiple of
491 * this class's remote interfaces contain methods with the
492 * same name and descriptor. Therefore, this MethodDoc may
493 * declare more exceptions thrown that this remote method
494 * does.
495 **/
496 private final MethodDoc methodDoc;
497
498 /** java.rmi.server.Operation string for this remote method */
499 private final String operationString;
500
501 /** name and descriptor of this remote method */
502 private final String nameAndDescriptor;
503
504 /** JRMP "method hash" for this remote method */
505 private final long methodHash;
506
507 /**
508 * Exceptions declared to be thrown by this remote method.
509 *
510 * This list may include superfluous entries, such as
511 * unchecked exceptions and subclasses of other entries.
512 **/
513 private ClassDoc[] exceptionTypes;
514
515 /**
516 * Creates a new Method instance for the specified method.
517 **/
518 Method(MethodDoc methodDoc) {
519 this.methodDoc = methodDoc;
520 exceptionTypes = methodDoc.thrownExceptions();
521 /*
522 * Sort exception types to improve consistency with
523 * previous implementations.
524 */
525 Arrays.sort(exceptionTypes, new ClassDocComparator());
526 operationString = computeOperationString();
527 nameAndDescriptor =
528 methodDoc.name() + Util.methodDescriptorOf(methodDoc);
529 methodHash = computeMethodHash();
530 }
531
532 /**
533 * Returns the MethodDoc object corresponding to this method
534 * of a remote interface.
535 **/
536 MethodDoc methodDoc() {
537 return methodDoc;
538 }
539
540 /**
541 * Returns the parameter types declared by this method.
542 **/
543 Type[] parameterTypes() {
544 Parameter[] parameters = methodDoc.parameters();
545 Type[] paramTypes = new Type[parameters.length];
546 for (int i = 0; i < paramTypes.length; i++) {
547 paramTypes[i] = parameters[i].type();
548 }
549 return paramTypes;
550 }
551
552 /**
553 * Returns the exception types declared to be thrown by this
554 * remote method.
555 *
556 * For methods with the same name and descriptor inherited
557 * from multiple remote interfaces, the array will contain the
558 * set of exceptions declared in all of the interfaces'
559 * methods that can be legally thrown by all of them.
560 **/
561 ClassDoc[] exceptionTypes() {
562 return (ClassDoc[]) exceptionTypes.clone();
563 }
564
565 /**
566 * Returns the JRMP "method hash" used to identify this remote
567 * method in the JDK 1.2 version of the stub protocol.
568 **/
569 long methodHash() {
570 return methodHash;
571 }
572
573 /**
574 * Returns the string representation of this method
575 * appropriate for the construction of a
576 * java.rmi.server.Operation object.
577 **/
578 String operationString() {
579 return operationString;
580 }
581
582 /**
583 * Returns a string consisting of this method's name followed
584 * by its descriptor.
585 **/
586 String nameAndDescriptor() {
587 return nameAndDescriptor;
588 }
589
590 /**
591 * Returns a new Method object that is a legal combination of
592 * this Method object and another one.
593 *
594 * Doing this requires determining the exceptions declared by
595 * the combined method, which must be (only) all of the
596 * exceptions declared in both old Methods that may thrown in
597 * either of them.
598 **/
599 Method mergeWith(Method other) {
600 if (!nameAndDescriptor().equals(other.nameAndDescriptor())) {
601 throw new AssertionError(
602 "attempt to merge method \"" +
603 other.nameAndDescriptor() + "\" with \"" +
604 nameAndDescriptor());
605 }
606
607 List<ClassDoc> legalExceptions = new ArrayList<ClassDoc>();
608 collectCompatibleExceptions(
609 other.exceptionTypes, exceptionTypes, legalExceptions);
610 collectCompatibleExceptions(
611 exceptionTypes, other.exceptionTypes, legalExceptions);
612
613 Method merged = clone();
614 merged.exceptionTypes =
615 legalExceptions.toArray(new ClassDoc[legalExceptions.size()]);
616
617 return merged;
618 }
619
620 /**
621 * Cloning is supported by returning a shallow copy of this
622 * object.
623 **/
624 protected Method clone() {
625 try {
626 return (Method) super.clone();
627 } catch (CloneNotSupportedException e) {
628 throw new AssertionError(e);
629 }
630 }
631
632 /**
633 * Adds to the supplied list all exceptions in the "froms"
634 * array that are subclasses of an exception in the "withs"
635 * array.
636 **/
637 private void collectCompatibleExceptions(ClassDoc[] froms,
638 ClassDoc[] withs,
639 List<ClassDoc> list)
640 {
641 for (ClassDoc from : froms) {
642 if (!list.contains(from)) {
643 for (ClassDoc with : withs) {
644 if (from.subclassOf(with)) {
645 list.add(from);
646 break;
647 }
648 }
649 }
650 }
651 }
652
653 /**
654 * Computes the JRMP "method hash" of this remote method. The
655 * method hash is a long containing the first 64 bits of the
656 * SHA digest from the UTF-8 encoded string of the method name
657 * and descriptor.
658 **/
659 private long computeMethodHash() {
660 long hash = 0;
661 ByteArrayOutputStream sink = new ByteArrayOutputStream(512);
662 try {
663 MessageDigest md = MessageDigest.getInstance("SHA");
664 DataOutputStream out = new DataOutputStream(
665 new DigestOutputStream(sink, md));
666
667 String methodString = nameAndDescriptor();
668 out.writeUTF(methodString);
669
670 // use only the first 64 bits of the digest for the hash
671 out.flush();
672 byte hashArray[] = md.digest();
673 for (int i = 0; i < Math.min(8, hashArray.length); i++) {
674 hash += ((long) (hashArray[i] & 0xFF)) << (i * 8);
675 }
676 } catch (IOException e) {
677 throw new AssertionError(e);
678 } catch (NoSuchAlgorithmException e) {
679 throw new AssertionError(e);
680 }
681
682 return hash;
683 }
684
685 /**
686 * Computes the string representation of this method
687 * appropriate for the construction of a
688 * java.rmi.server.Operation object.
689 **/
690 private String computeOperationString() {
691 /*
692 * To be consistent with previous implementations, we use
693 * the deprecated style of placing the "[]" for the return
694 * type (if any) after the parameter list.
695 */
696 Type returnType = methodDoc.returnType();
697 String op = returnType.qualifiedTypeName() + " " +
698 methodDoc.name() + "(";
699 Parameter[] parameters = methodDoc.parameters();
700 for (int i = 0; i < parameters.length; i++) {
701 if (i > 0) {
702 op += ", ";
703 }
704 op += parameters[i].type().toString();
705 }
706 op += ")" + returnType.dimension();
707 return op;
708 }
709 }
710}