001: /*
002: * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.rmi.rmic.newrmic.jrmp;
027:
028: import com.sun.javadoc.ClassDoc;
029: import com.sun.javadoc.MethodDoc;
030: import com.sun.javadoc.Parameter;
031: import com.sun.javadoc.Type;
032: import java.io.IOException;
033: import java.io.ByteArrayOutputStream;
034: import java.io.DataOutputStream;
035: import java.security.MessageDigest;
036: import java.security.DigestOutputStream;
037: import java.security.NoSuchAlgorithmException;
038: import java.util.ArrayList;
039: import java.util.Arrays;
040: import java.util.Comparator;
041: import java.util.List;
042: import java.util.HashMap;
043: import java.util.Map;
044: import sun.rmi.rmic.newrmic.BatchEnvironment;
045:
046: import static sun.rmi.rmic.newrmic.Constants.*;
047: import static sun.rmi.rmic.newrmic.jrmp.Constants.*;
048:
049: /**
050: * Encapsulates RMI-specific information about a remote implementation
051: * class (a class that implements one or more remote interfaces).
052: *
053: * WARNING: The contents of this source file are not part of any
054: * supported API. Code that depends on them does so at its own risk:
055: * they are subject to change or removal without notice.
056: *
057: * @version 1.9, 07/05/05
058: * @author Peter Jones
059: **/
060: final class RemoteClass {
061:
062: /** rmic environment for this object */
063: private final BatchEnvironment env;
064:
065: /** the remote implementation class this object represents */
066: private final ClassDoc implClass;
067:
068: /** remote interfaces implemented by this class */
069: private ClassDoc[] remoteInterfaces;
070:
071: /** the remote methods of this class */
072: private Method[] remoteMethods;
073:
074: /** stub/skeleton "interface hash" for this class */
075: private long interfaceHash;
076:
077: /**
078: * Creates a RemoteClass instance that represents the RMI-specific
079: * information about the specified remote implementation class.
080: *
081: * If the class is not a valid remote implementation class or if
082: * some other error occurs, the return value will be null, and
083: * errors will have been reported to the supplied
084: * BatchEnvironment.
085: **/
086: static RemoteClass forClass(BatchEnvironment env, ClassDoc implClass) {
087: RemoteClass remoteClass = new RemoteClass(env, implClass);
088: if (remoteClass.init()) {
089: return remoteClass;
090: } else {
091: return null;
092: }
093: }
094:
095: /**
096: * Creates a RemoteClass instance for the specified class. The
097: * resulting object is not yet initialized.
098: **/
099: private RemoteClass(BatchEnvironment env, ClassDoc implClass) {
100: this .env = env;
101: this .implClass = implClass;
102: }
103:
104: /**
105: * Returns the ClassDoc for this remote implementation class.
106: **/
107: ClassDoc classDoc() {
108: return implClass;
109: }
110:
111: /**
112: * Returns the remote interfaces implemented by this remote
113: * implementation class.
114: *
115: * A remote interface is an interface that is a subinterface of
116: * java.rmi.Remote. The remote interfaces of a class are the
117: * direct superinterfaces of the class and all of its superclasses
118: * that are remote interfaces.
119: *
120: * The order of the array returned is arbitrary, and some elements
121: * may be superfluous (i.e., superinterfaces of other interfaces
122: * in the array).
123: **/
124: ClassDoc[] remoteInterfaces() {
125: return (ClassDoc[]) remoteInterfaces.clone();
126: }
127:
128: /**
129: * Returns an array of RemoteClass.Method objects representing all
130: * of the remote methods of this remote implementation class (all
131: * of the member methods of the class's remote interfaces).
132: *
133: * The methods in the array are ordered according to a comparison
134: * of strings consisting of their name followed by their
135: * descriptor, so each method's index in the array corresponds to
136: * its "operation number" in the JDK 1.1 version of the JRMP
137: * stub/skeleton protocol.
138: **/
139: Method[] remoteMethods() {
140: return (Method[]) remoteMethods.clone();
141: }
142:
143: /**
144: * Returns the "interface hash" used to match a stub/skeleton pair
145: * for this remote implementation class in the JDK 1.1 version of
146: * the JRMP stub/skeleton protocol.
147: **/
148: long interfaceHash() {
149: return interfaceHash;
150: }
151:
152: /**
153: * Validates this remote implementation class and computes the
154: * RMI-specific information. Returns true if successful, or false
155: * if an error occurred.
156: **/
157: private boolean init() {
158: /*
159: * Verify that it is really a class, not an interface.
160: */
161: if (implClass.isInterface()) {
162: env.error("rmic.cant.make.stubs.for.interface", implClass
163: .qualifiedName());
164: return false;
165: }
166:
167: /*
168: * Find all of the remote interfaces of our remote
169: * implementation class-- for each class up the superclass
170: * chain, add each directly-implemented interface that somehow
171: * extends Remote to a list.
172: */
173: List<ClassDoc> remotesImplemented = new ArrayList<ClassDoc>();
174: for (ClassDoc cl = implClass; cl != null; cl = cl.super class()) {
175: for (ClassDoc intf : cl.interfaces()) {
176: /*
177: * Add interface to the list if it extends Remote and
178: * it is not already there.
179: */
180: if (!remotesImplemented.contains(intf)
181: && intf.subclassOf(env.docRemote())) {
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", implClass
209: .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 = remotesImplemented
220: .toArray(new ClassDoc[remotesImplemented.size()]);
221:
222: /*
223: * Collect the methods from all of the remote interfaces into
224: * a table, which maps from method name-and-descriptor string
225: * to Method object.
226: */
227: Map<String, Method> methods = new HashMap<String, Method>();
228: boolean errors = false;
229: for (ClassDoc intf : remotesImplemented) {
230: if (!collectRemoteMethods(intf, methods)) {
231: /*
232: * Continue iterating despite errors in order to
233: * generate more complete error report.
234: */
235: errors = true;
236: }
237: }
238: if (errors) {
239: return false;
240: }
241:
242: /*
243: * Sort table of remote methods into an array. The elements
244: * are sorted in ascending order of the string of the method's
245: * name and descriptor, so that each elements index is equal
246: * to its operation number in the JDK 1.1 version of the JRMP
247: * stub/skeleton protocol.
248: */
249: String[] orderedKeys = methods.keySet().toArray(
250: new String[methods.size()]);
251: Arrays.sort(orderedKeys);
252: remoteMethods = new Method[methods.size()];
253: for (int i = 0; i < remoteMethods.length; i++) {
254: remoteMethods[i] = methods.get(orderedKeys[i]);
255: if (env.verbose()) {
256: String msg = "[found remote method <" + i + ">: "
257: + remoteMethods[i].operationString();
258: ClassDoc[] exceptions = remoteMethods[i]
259: .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: if (!intf.isInterface()) {
296: throw new AssertionError(intf.qualifiedName()
297: + " not an interface");
298: }
299:
300: boolean errors = false;
301:
302: /*
303: * Search interface's declared methods.
304: */
305: nextMethod: for (MethodDoc method : intf.methods()) {
306:
307: /*
308: * Verify that each method throws RemoteException (or a
309: * superclass of RemoteException).
310: */
311: boolean hasRemoteException = false;
312: for (ClassDoc ex : method.thrownExceptions()) {
313: if (env.docRemoteException().subclassOf(ex)) {
314: hasRemoteException = true;
315: break;
316: }
317: }
318:
319: /*
320: * If this method did not throw RemoteException as required,
321: * generate the error but continue, so that multiple such
322: * errors can be reported.
323: */
324: if (!hasRemoteException) {
325: env.error("rmic.must.throw.remoteexception", intf
326: .qualifiedName(), method.name()
327: + method.signature());
328: errors = true;
329: continue nextMethod;
330: }
331:
332: /*
333: * Verify that the implementation of this method throws only
334: * java.lang.Exception or its subclasses (fix bugid 4092486).
335: * JRMP does not support remote methods throwing
336: * java.lang.Throwable or other subclasses.
337: */
338: MethodDoc implMethod = findImplMethod(method);
339: if (implMethod != null) { // should not be null
340: for (ClassDoc ex : implMethod.thrownExceptions()) {
341: if (!ex.subclassOf(env.docException())) {
342: env.error("rmic.must.only.throw.exception",
343: implMethod.name()
344: + implMethod.signature(), ex
345: .qualifiedName());
346: errors = true;
347: continue nextMethod;
348: }
349: }
350: }
351:
352: /*
353: * Create RemoteClass.Method object to represent this method
354: * found in a remote interface.
355: */
356: Method newMethod = new Method(method);
357:
358: /*
359: * Store remote method's representation in the table of
360: * remote methods found, keyed by its name and descriptor.
361: *
362: * If the table already contains an entry with the same
363: * method name and descriptor, then we must replace the
364: * old entry with a Method object that represents a legal
365: * combination of the old and the new methods;
366: * specifically, the combined method must have a throws
367: * clause that contains (only) all of the checked
368: * exceptions that can be thrown by both the old and the
369: * new method (see bugid 4070653).
370: */
371: String key = newMethod.nameAndDescriptor();
372: Method oldMethod = table.get(key);
373: if (oldMethod != null) {
374: newMethod = newMethod.mergeWith(oldMethod);
375: }
376: table.put(key, newMethod);
377: }
378:
379: /*
380: * Recursively collect methods for all superinterfaces.
381: */
382: for (ClassDoc super intf : intf.interfaces()) {
383: if (!collectRemoteMethods(super intf, table)) {
384: errors = true;
385: }
386: }
387:
388: return !errors;
389: }
390:
391: /**
392: * Returns the MethodDoc for the method of this remote
393: * implementation class that implements the specified remote
394: * method of a remote interface. Returns null if no matching
395: * method was found in this remote implementation class.
396: **/
397: private MethodDoc findImplMethod(MethodDoc interfaceMethod) {
398: String name = interfaceMethod.name();
399: String desc = Util.methodDescriptorOf(interfaceMethod);
400: for (MethodDoc implMethod : implClass.methods()) {
401: if (name.equals(implMethod.name())
402: && desc.equals(Util.methodDescriptorOf(implMethod))) {
403: return implMethod;
404: }
405: }
406: return null;
407: }
408:
409: /**
410: * Computes the "interface hash" of the stub/skeleton pair for
411: * this remote implementation class. This is the 64-bit value
412: * used to enforce compatibility between a stub class and a
413: * skeleton class in the JDK 1.1 version of the JRMP stub/skeleton
414: * protocol.
415: *
416: * It is calculated using the first 64 bits of an SHA digest. The
417: * digest is of a stream consisting of the following data:
418: * (int) stub version number, always 1
419: * for each remote method, in order of operation number:
420: * (UTF-8) method name
421: * (UTF-8) method descriptor
422: * for each declared exception, in alphabetical name order:
423: * (UTF-8) name of exception class
424: * (where "UTF-8" includes a 16-bit length prefix as written by
425: * java.io.DataOutput.writeUTF).
426: **/
427: private long computeInterfaceHash() {
428: long hash = 0;
429: ByteArrayOutputStream sink = new ByteArrayOutputStream(512);
430: try {
431: MessageDigest md = MessageDigest.getInstance("SHA");
432: DataOutputStream out = new DataOutputStream(
433: new DigestOutputStream(sink, md));
434:
435: out.writeInt(INTERFACE_HASH_STUB_VERSION);
436:
437: for (Method method : remoteMethods) {
438: MethodDoc methodDoc = method.methodDoc();
439:
440: out.writeUTF(methodDoc.name());
441: out.writeUTF(Util.methodDescriptorOf(methodDoc));
442: // descriptors already use binary names
443:
444: ClassDoc exceptions[] = methodDoc.thrownExceptions();
445: Arrays.sort(exceptions, new ClassDocComparator());
446: for (ClassDoc ex : exceptions) {
447: out.writeUTF(Util.binaryNameOf(ex));
448: }
449: }
450: out.flush();
451:
452: // use only the first 64 bits of the digest for the hash
453: byte hashArray[] = md.digest();
454: for (int i = 0; i < Math.min(8, hashArray.length); i++) {
455: hash += ((long) (hashArray[i] & 0xFF)) << (i * 8);
456: }
457: } catch (IOException e) {
458: throw new AssertionError(e);
459: } catch (NoSuchAlgorithmException e) {
460: throw new AssertionError(e);
461: }
462:
463: return hash;
464: }
465:
466: /**
467: * Compares ClassDoc instances according to the lexicographic
468: * order of their binary names.
469: **/
470: private static class ClassDocComparator implements
471: Comparator<ClassDoc> {
472: public int compare(ClassDoc o1, ClassDoc o2) {
473: return Util.binaryNameOf(o1).compareTo(
474: 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 = methodDoc.name()
528: + 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("attempt to merge method \""
602: + other.nameAndDescriptor() + "\" with \""
603: + nameAndDescriptor());
604: }
605:
606: List<ClassDoc> legalExceptions = new ArrayList<ClassDoc>();
607: collectCompatibleExceptions(other.exceptionTypes,
608: exceptionTypes, legalExceptions);
609: collectCompatibleExceptions(exceptionTypes,
610: other.exceptionTypes, legalExceptions);
611:
612: Method merged = clone();
613: merged.exceptionTypes = legalExceptions
614: .toArray(new ClassDoc[legalExceptions.size()]);
615:
616: return merged;
617: }
618:
619: /**
620: * Cloning is supported by returning a shallow copy of this
621: * object.
622: **/
623: protected Method clone() {
624: try {
625: return (Method) super .clone();
626: } catch (CloneNotSupportedException e) {
627: throw new AssertionError(e);
628: }
629: }
630:
631: /**
632: * Adds to the supplied list all exceptions in the "froms"
633: * array that are subclasses of an exception in the "withs"
634: * array.
635: **/
636: private void collectCompatibleExceptions(ClassDoc[] froms,
637: ClassDoc[] withs, List<ClassDoc> list) {
638: for (ClassDoc from : froms) {
639: if (!list.contains(from)) {
640: for (ClassDoc with : withs) {
641: if (from.subclassOf(with)) {
642: list.add(from);
643: break;
644: }
645: }
646: }
647: }
648: }
649:
650: /**
651: * Computes the JRMP "method hash" of this remote method. The
652: * method hash is a long containing the first 64 bits of the
653: * SHA digest from the UTF-8 encoded string of the method name
654: * and descriptor.
655: **/
656: private long computeMethodHash() {
657: long hash = 0;
658: ByteArrayOutputStream sink = new ByteArrayOutputStream(512);
659: try {
660: MessageDigest md = MessageDigest.getInstance("SHA");
661: DataOutputStream out = new DataOutputStream(
662: new DigestOutputStream(sink, md));
663:
664: String methodString = nameAndDescriptor();
665: out.writeUTF(methodString);
666:
667: // use only the first 64 bits of the digest for the hash
668: out.flush();
669: byte hashArray[] = md.digest();
670: for (int i = 0; i < Math.min(8, hashArray.length); i++) {
671: hash += ((long) (hashArray[i] & 0xFF)) << (i * 8);
672: }
673: } catch (IOException e) {
674: throw new AssertionError(e);
675: } catch (NoSuchAlgorithmException e) {
676: throw new AssertionError(e);
677: }
678:
679: return hash;
680: }
681:
682: /**
683: * Computes the string representation of this method
684: * appropriate for the construction of a
685: * java.rmi.server.Operation object.
686: **/
687: private String computeOperationString() {
688: /*
689: * To be consistent with previous implementations, we use
690: * the deprecated style of placing the "[]" for the return
691: * type (if any) after the parameter list.
692: */
693: Type returnType = methodDoc.returnType();
694: String op = returnType.qualifiedTypeName() + " "
695: + methodDoc.name() + "(";
696: Parameter[] parameters = methodDoc.parameters();
697: for (int i = 0; i < parameters.length; i++) {
698: if (i > 0) {
699: op += ", ";
700: }
701: op += parameters[i].type().toString();
702: }
703: op += ")" + returnType.dimension();
704: return op;
705: }
706: }
707: }
|