This document is intended to enable an early review by interested parties of the design of the IDL Java mapping. Please send comments to:
idl-java@wombat.eng.sun.com
Please note that some of the material in this document is likely to change as a result of further prototyping work and possibly as a result of feedback. This version of the mapping is fully consistent with the Java Developer's Kit 1.0.
The document provides the design rationale for the proposed mapping from IDL to Java. The handling of dynamic types (Any, typecodes, DII, and DSI) remain to be added.
The reader is assumed to be familiar with OMG IDL (for which, see ``The Common Object Request Broker: Architecture and Specification'', Revision 2.0, July 1995) and the Java Language (for which, see ftp://ftp.javasoft.com/docs/javaspec.ps.tar.Z until something better shows up).
Among the goals for this mapping was that it be ``natural'' for Java programmers. Alternatives included slavishly following the style used by, for example, the mapping from IDL to C++. Where we thought we could do things better in this mapping, or in Java, we did what we thought Java programmers would appreciate.
Keep in mind that the Java language is still undergoing some change as it moves into its final released form, and we will revise this mapping to match (and exploit) changes in the Java language.
Each section describes an IDL feature and describes how it is mapped
to Java. Each section includes an English description of the mapping, an
example of the feature in IDL and as it is mapped into Java, and usually
some rationale for our choice of mapping. Occasionally there are
implementation limitations, or usage suggestions for the mapping. These
will also be described. We reserve the right to remove limitations in
the future. Where alternatives were considered, those will also be
described in an attempt to provoke discussion of possible alternative
mappings, and to provide design history so decisions are not revisited
unintentionally. Except as noted, the examples of the mapping are taken
directly from the output of our IDL-to-Java compiler, so they are
faithful as of the date of the document. Where the details of the
generated code are unimportant, they will be elided with the symbol
``....
''.
Each IDL module is mapped to a Java package. The Java package has the same name as the IDL module. For all IDL types within the IDL module that are mapped to Java classes or Java interfaces, the corresponding Java class or Java interface are declared inside the generated Java package.
Anticipating the mapping of IDL enums:
/* From Example.idl: */ module Example { enum EnumType { .... }; };
// Generated to Example/EnumType.java: package Example; public class EnumType { .... }
IDL modules are a name-scoping mechanism. The corresponding name-scoping mechanism in Java is the Java package.
To cover the case where IDL declarations are not within an IDL module, the mapping uses a Java package to hold those declarations. The Java package is named ``idlGlobal''. Declarations are added to this Java package for any IDL declarations that are not in an IDL module. It is possible to over-write the mapping of one IDL declaration (from one compilation unit) with the mapping for another IDL declaration of the same name (from another compilation unit). Detecting such name collisions is left to the IDL development system.
An alternative mapping tries to map IDL declarations that are not in an IDL module to a Java package named for the compilation unit. This is at least as awkward as mapping all such IDL declarations to a single Java package, and is harder to explain to people and more difficult to remember and use. The single Java package mimics the collisions that happen because two IDL declarations have the same name.
People should be encouraged to put IDL declarations inside of IDL modules.
All the IDL examples shown will be from the IDL module Example. Therefore, the generated Java declarations will all be inside the Java package Example.
IDL constant values are mapped to public static final instances in Java. The name of the IDL const is used as the name of a Java class, and the value is available as the instance variable ``value''.
Anticipating that an IDL long is mapped to a Java int (see below)
/* From Example.idl: */ module Example { const long aLong = -12345; };
// Generated to Example/aLong.java: package Example; public final class aLong { public static final int value = (int) (-12345L); }
A client of Example::aLong would reference the value as
Example.aLong.value
At least three Java name components are needed to refer to a constant value. The constant itself must be an Java instance in a Java class, and because the Java language makes it awkward to refer to unpackaged Java classes from within packaged Java classes, the class for the mapping should be in a Java package. Since IDL constants can appear with fewer than three IDL name components, some mapping that adds name components is needed, at least some of the time. Rather than have different rules for different IDL constants, we adopted the simple mapping show above, which, with the mapping for IDL declarations that are not in IDL modules guarantees at least three components to the mapped constant.
A possible objection to the simple mapping is that it generates a Java class for each IDL const. The alternative is to generate one Java class that holds all the IDL consts for a given IDL scope. On the one hand, we would like to minimize the number of Java classes the mapping generates. On the other hand, our mapping to a public static final int allows the value to be inlined into any Java uses of the IDL const, so the class file for the IDL const need not be loaded at run time. The disadvantage of using one Java class for all the IDL consts in a scope is that the Java class for IDL consts that are not in IDL modules cannot be extended as more IDL compilation units are mapped to Java.
This section describes the mapping of the IDL basic types:
boolean, char, octet, string, and the integer and floating point
types.
Boolean
The IDL type boolean
is mapped to the Java type
boolean
. The IDL constants TRUE
and
FALSE
are mapped to the Java constants true
and false
.
/* From Example.idl: */ module Example { const boolean truth = TRUE; };
// Generated to Example/truth.java: package Example; public final class truth { public static final boolean value = (true); }
The IDL type char
is mapped to the Java type
char
.
/* From Example.idl: */ module Example { const char aChar = 'A'; };
// Generated to Example/aChar.java: package Example; public final class aChar { public static final char value = ('A'); }
IDL char
s come from the ISO 8859.1 character set,
having at most 255 different values. Java char
s come from
the Unicode character set, having at most 65536 different values. IDL
characters occupy a small corner of the Java character space. During
marshaling Java char
s are checked to make sure they are in
the ISO 8859.1 subset of Unicode. If a Java char
is found
to be outside the range of ISO 8859.1, an
omg.corba.CharacterRangeException is thrown.
The fact that Java char
s are at least as wide as IDL
char
s meets the needs of an IDL mapping for
char
s. The unfortunate part is that Java programs can form
Java char
s that are outside the range of IDL
char
s, necessitating a range check during marshaling.
According to the IDL specification, we should map the IDL
characters in a way that expresses the meaning of the IDL characters
in Java characters but preserves the round-trip values of those
characters (e.g. the characters as defined in Tables 2, 3, 4, and 5 of
the CORBA 2.0 specification Section 3.2, see Section 3.8.1), but doing
anything exotic in the way of translation seems likely to break
things.
Octet
The IDL type octet
is mapped to the Java type
byte
.
/* From Example.idl: */ module Example { const octet anOctet = 42; };
// Generated to Example/anOctet.java: package Example; public final class anOctet { public static final byte value = (byte) (42L); }
The IDL string
type is mapped to the Java type
java.lang.String
. Recall that
java.lang.String
s contain Unicode characters, which are
wider than IDL char
s. When marshaled, the characters of the
IDL string are checked to make sure they are in the ISO 8859.1 subset of
Unicode, and an omg.corba.CharacterRangeException exception may be
thrown.
/* From Example.idl: */ module Example { const string aString = "Hello world!"; };
// Generated to Example/aString.java: package Example; public final class aString { public static final String value = ("Hello world!"); }
Since java.lang.Strings
are unbounded, IDL bounded
strings have their length checked when marshaling and unmarshaling as
parameters. However, their length is not checked at other times. You
can check the length of the java.lang.String
instance at
any time. (If you want the bound of a bounded IDL string to be
available at run time, declare an IDL constant with the bound and use
that IDL constant as the bound of the bounded IDL string. The IDL
constant will be available in Java through its mapping.)
Both bounded and unbounded IDL string
s are mapped to
java.lang.String
s, which are unbounded (but immutable). We
could have mapped IDL string
s to a class that implemented a
sequence of characters, but decided it would be more ``natural'' for
Java programmers to use java.lang.String
s. That allows Java
programmers to use double-quoted java.lang.String
constants
and the rich set of operations on java.lang.String
s.
Integers
IDL includes six integer data types: 16-bit, 32-bit, and 64-bit
sizes, in both signed and unsigned varieties. Java has three integral
types: short
, int
, and long
. The
IDL integer data types are mapped to the correspondingly-sized Java
integer data types.
/* From Example.idl: */ module Example { const short aShort = -1; const unsigned short anUnsignedShort = 15907; const long aLong = -12345; const unsigned long anUnsignedLong = 901008; const long long aLongLong = -1234567890; const unsigned long long anUnsignedLongLong = 987654321; };
// Generated to Example/aShort.java: package Example; public final class aShort { public static final short value = (short) (-1L); } // Generated to Example/anUnsignedShort.java: package Example; public final class anUnsignedShort { public static final short value = (short) (15907L); } // Generated to Example/aLong.java: package Example; public final class aLong { public static final int value = (int) (-12345L); } // Generated to Example/anUnsignedLong.java: package Example; public final class anUnsignedLong { public static final int value = (int) (901008L); } // Generated to Example/aLongLong.java: package Example; public final class aLongLong { public static final long value = (long) (-1234567890L); } // Generated to Example/anUnsignedLongLong.java: package Example; public final class anUnsignedLongLong { public static final long value = (long) (987654321L); }
Mapping the IDL signed integer types to the corresponding Java integer types is uncontroversial.
The lack of unsigned integer types in Java forces a choice. Either we can map the unsigned IDL integer types to the next larger Java integer type to try to preserve the magnitude of the integer, or we can map IDL unsigned types to the correspondingly-sized Java integer types and leave the user to figure out that negative signed values are actually large unsigned values, where appropriate.
Mapping up to the next larger size doesn't extend to mapping the IDL
unsigned long long
type, since Java doesn't have an integer
type larger than 64 bits. It would also be undesirable to have the IDL
unsigned integer types map to differently-sized Java integer types than
the corresponding IDL signed integer types. If IDL integers are mapped
to the corresponding Java integer types, there is a potential loss of
specification, in that negative values can be stored in what should be
IDL unsigned integers. If IDL integers are mapped to larger Java integer
types there is a potential loss of specification in that numbers outside
the range of an IDL unsigned integer type could be stored in the larger
Java integer type. In the end we decided that 32-bit quantities should
occupy 32 bits, and so on. This is the ``natural'' mapping for Java
programmers.
The major difference between signed and unsigned integers in
programs comes when making comparisons (also when computing a
modulus). If necessary, we should encourage the Java language run time
libraries to provide unsigned methods for java.lang.Integer and
java.lang.Long. If not, we can provide those methods ourselves.
Floating Points
IDL floating point types map to the corresponding Java floating point types. Both languages use ANSI/IEEE 754-1985 floating point types, so this decision is uncontroversial.
/* From Example.idl: */ module Example { const float aFloat = 2.71828; const double aDouble = 3.14159265358979323846; };
// Generated to Example/aFloat.java: package Example; public final class aFloat { public static final float value = (float) (2.71828D); } // Generated to Example/aDouble.java: package Example; public final class aDouble { public static final double value = (double) (3.14159265358979323846D); }
This section describes the mapping of IDL constructed types: enum,
struct, union, sequence, and array. Sequences and arrays are mapped to
Java arrays. Each of the other IDL constructed types is mapped to a Java
class that implements the semantics of the IDL type. The name of the
generated Java class is the name of the IDL type.
Enum
Each IDL enum type is mapped to a Java class that defines a Java static final int with the value of each enum member, and a method to check that a Java int is in the range defined by the IDL enum. The name of the class is the name of the IDL enum type.
/* From Example.idl: */ module Example { enum EnumType { none, first, second, third, fourth }; };
// Generated to Example/EnumType.java: package Example; public class EnumType { public static final int none = 0, first = 1, second = 2, third = 3, fourth = 4; public static final int narrow(int i) throws omg.corba.EnumerationRangeException { .... } }
The narrow
method either returns the argument value or
throws an exception, so it can be used inline in expressions to verify
that Java int's are in the range of the IDL enum. The exception
omg.corba.EnumerationRangeException extends omg.corba.RuntimeException,
which extends java.lang.RuntimeException, so the exception need not be
declared in any methods that call the narrow method.
The purpose of an enumeration is to indicate a choice from within a set of alternatives. As such, the obvious mapping is to a range of Java int's and the selection among alternatives is made with a Java switch statement.
An alternative mapping makes the IDL enum a Java class, with the members of the IDL enum represented by run time instances of the Java class. The Java class has a method that reveals the position of each Java instance in the IDL enum. (A private Java constructor prevents any additional instances from being created.) The problem with this mapping is that the instances cannot be used in the most obvious application of enum members: as case labels in Java switch statements. Instead, to select among the alternatives, one would invoke the method to determine the rank in the enumeration of the instance in hand, and transfer through a switch statement whose labels were the various ranks. Having names for the ranks that can be used to label switch cases seems like a more natural mapping.
Another alternative mapping makes the IDL enum an abstract Java
class, with one method: int getValue(). Then each member of the IDL
enum is mapped to a member Java class that extends the abstract Java
class, providing the implementation of the getValue method (to return
the position of the member in the enum). Clients can declare
variables of the enum Java class and assign to it instances of the
member Java class and all the assignments are type checked. (This
approach is like the immutable union approach, except it makes more
sense here since enum members are immutable.) The member classes also
provide a public static final int set to the position of the member.
The public static final int value can be used to label switch
statement cases. So everyone is happy. Well, almost everyone;
because now we have a class for each enum member, which might be a lot
of classes.
Struct
An IDL struct is mapped to a Java class that provides instance variables for the fields and a constructor from values (also a null constructor so the fields can be filled in later).
/* From Example.idl: */ module Example { struct StructType { long these; string those; }; };
// Generated to Example/StructType.java: package Example; public final class StructType { // instance variables public int these; public String those; // constructors public StructType() { } public StructType(int __these, String __those) { .... } }
We could have implemented methods on the mapped class to provide
access to the instance variables (and made the instance variables
private). That's more in the ``style'' of recent Java code. Leaving
the instance variables exposed prevents us from interposing
interesting accessor methods in the future without changing the
mapping, but generates less code and allows faster access than if
accessor methods were used. One should also remember that if you
wanted accessor methods for fields one could declare an IDL interface
with attributes. The purpose of IDL structs is to define data that
will be directly accessible, so we left the fields exposed.
Union
An IDL union is mapped to a Java class that provides a default constructor, an accessor method for the discriminator, an accessor method for each of the branches, and a modifier method for each of the branches. The Java class is named by the name of the IDL union. The accessor method for a branch is named by the name of the branch with the prefix ``get''. The modifier method for a branch is named by the name of the branch with the prefix ``set''.
If an IDL union branch has a non-default case label, then a static method is generated to construct an instance from an instance of the branch type. The static constructor is named by the name of the branch with the prefix ``create''. In addition, a modifier method is generated that does not require a discriminator parameter. The single-argument modifier method is named by the name of the branch with the prefix ``set'' (this overloads the mandatory modifier method).
/* From Example.idl: */ module Example { union UnionType switch (EnumType) { case first: long Win; case second: short Place; case third: octet Show; case fourth: default: boolean Other; }; };
// Generated to Example/UnionType.java: package Example; public class UnionType { // constructor public UnionType() { .... } // discriminator accessor public int discriminator() throws omg.corba.UnionDiscriminantException { .... } // branch constructors and get and set accessors // Win public static UnionType createWin(int value) { .... } public int getWin() throws omg.corba.UnionDiscriminantException { .... } public void setWin(int value) { .... } public void setWin(int discriminator, int value) throws omg.corba.UnionDiscriminantException { .... } // Place public static UnionType createPlace(short value) { .... } public short getPlace() throws omg.corba.UnionDiscriminantException { .... } public void setPlace(short value) { .... } public void setPlace(int discriminator, short value) throws omg.corba.UnionDiscriminantException { .... } // Show public static UnionType createShow(byte value) { .... } public byte getShow() throws omg.corba.UnionDiscriminantException { .... } public void setShow(byte value) { .... } public void setShow(int discriminator, byte value) throws omg.corba.UnionDiscriminantException { .... } // Other public static UnionType createOther(boolean value) { .... } public boolean getOther() throws omg.corba.UnionDiscriminantException { .... } public void setOther(boolean value) { .... } public void setOther(int discriminator, boolean value) throws omg.corba.UnionDiscriminantException { .... } }
The default constructor leaves the union in an uninitialized state. Invoking accessor methods, including the discriminator accessor, on uninitialized unions will throw an omg.corba.UnionDiscriminantException. That exception is an omg.corba.RuntimeException, and so need not be declared in methods that call any of the methods on a mapped union.
The branch constructors initialize the discriminator to an unspecified member of the case labels for the branch. If a branch has only a single non-default case label, then that label is used as the implicit discriminator.
The value for the current branch can be accessed by calling the accessor method for the current branch. Attempts to call an accessor for other than the current branch will throw omg.corba.UnionDiscriminantException.
A union can be initialized (or changed after initialization) by calling one of the branch modifier methods. Branch modifier methods can come in two forms. If the branch has a non-default label, then a single-argument modifier method is generated that supplies a discriminator that is appropriate for the branch, and sets the branch to the single argument. In any case, a two argument modifier method is generated that takes both a discriminator value and a branch value. Attempts to supply a discriminator that is not valid for the branch will throw omg.corba.UnionDiscriminantException.
Note that the discriminator cannot be set without supplying an appropriate value for the branch. If one wants to change the discriminator without changing the branch value then one should pass the new discriminator and the current branch value to the branch modifier method. If one wants to change the branch value without changing the discriminator then one should pass the current discriminator and the new branch value to the branch modifier method.
IDL unions must be type-safe in Java, since Java is a type-safe language. Therefore there the correspondence between the discriminator and the branch value must be tightly coupled. This need to control the modification of the discriminator and the branch values rules out a mapping with exposed discriminator or branch instances, or separate discriminator and branch modifier methods.
An alternative mapping has the IDL union mapped to a Java class, and
each of the branches mapped to a Java class that extends the Java class
for the union. Once the branch of a union instance is set (by calling
an extending Java class constructor), which branch the union instance
represents is immutable. The accessor and modifier methods on the
extending Java classes are as strongly typed as the accessors in the
current mapping (except that they have simpler names, since the name of
the branch is encoded in the name of the extending Java classes). The
discrimination of the branches can be performed using the Java run time
typing information. An advantage of the extending Java classes is that
once the branch is determined it can be passed around as the extending
Java class, avoiding some run time checks. A disadvantage of the
extending Java classes is that in order to access or modify the value
the instance must be cast to the appropriate extending Java class.
Another disadvantage is that more Java classes are generated, though
only up to the complexity of the user's IDL union declaration. The big
difference between these approaches is whether a union instance can be
changed from one branch to another.
Sequence
Each IDL sequence is mapped to a Java array. In the mapping, everywhere the sequence type is needed, an array of the mapped type of the element is used. Bounded sequences have their bounds checked when they are marshaled as parameters to IDL operations.
Recalling the mapping for IDL structs:
/* From Example.idl: */ module Example { struct SequenceContainer { sequence< StructType > unbounded; sequence< StructType, 42 > bounded; }; };
// Generated to Example/SequenceContainer.java: package Example; public final class SequenceContainer { // instance variables public Example.StructType[] unbounded; public Example.StructType[] bounded; // constructors public SequenceContainer() { } public SequenceContainer(Example.StructType[] __unbounded, Example.StructType[] __bounded) { .... } }
The mapping to Java arrays allows the natural Java subscripting
operator to be applied to mapped IDL sequences. Java arrays can be
resized by allocating a new array instance. This is convenient for
unbounded sequences, but awkward for bounded sequences. The bounds on
bounded sequences are checked when the sequence is passed as a parameter
to an IDL operation, but not otherwise. The alternative is to map
bounded sequences to a class with a subscript method (not "[]"s) that
would provide access to a private instance array of elements. The
instance would be private to prevent it from being resized (i.e.,
reallocated with a different size). The choice of having different
mappings for bounded and unbounded sequences, and losing the
subscripting operator for bounded sequences, seems worse than only
checking the bounds of the bounded arrays as they crosses an IDL
boundary.
Array
An IDL array is mapped the same way as an IDL bounded sequence is mapped. This mapping allows the natural Java subscripting operator to be applied to the mapped array. The bounds for the array are checked when the array is marshaled as an argument to an IDL operation. If you want the length of the array to be available in Java, bound the array with an IDL constant, which will be available through its mapping.
/* From Example.idl: */ module Example { const long ArrayBound = 42; struct ArrayContainer { long array[ArrayBound]; }; };
// Generated to Example/ArrayContainer.java: package Example; public final class ArrayContainer { // instance variables public int[] array; // constructors public ArrayContainer() { } public ArrayContainer(int[] __array) { .... } }
IDL user-defined exceptions are mapped essentially as IDL structs are mapped. A Java class is generated that provides instance variables for the fields of the exception, and a constructor for the exception from the fields (and a default constructor). All IDL user-defined exceptions extend the Java class omg.corba.UserException (which in turn extends omg.corba.CORBAException, which in turn extends java.lang.Exception), which makes it possible to catch specific user-defined exceptions, or to catch all user-defined exceptions by catching omg.corba.UserException, or to catch all user-defined exceptions and system exceptions by catching omg.corba.CORBAException.
/* From Example.idl: */ module Example { exception That { string reason; }; };
// Generated to Example/That.java: package Example; public class That extends omg.corba.UserException { // instance variables public String reason; // constructors public That() { .... } public That(String __reason) { .... } }
This section describes the mapping for IDL interfaces. The description comes in several parts. First there's a discussion of interfaces versus object references. Next is a discussion of inheritance. Then comes the mapping for operations on interfaces. Last comes the mapping for parameter passing modes.
In IDL, users can define interfaces to objects. In the mapping to any particular language, clients operate on references to objects, via object references that are made available in the mapping language. In the mapping to Java, each user-defined IDL interface maps to two Java interfaces and a Java class.
One Java interface gives the signatures of the operations directly defined in the IDL interface. That Java interface will be referred to as the operations Java interface. The other Java interface describes any IDL interfaces that are inherited by this IDL interface. That Java interface will be referred to as the object reference Java interface. The object reference Java interface extends the operations from the IDL interface with some additional methods that are available on the object reference. Java instances that support the Java object reference interface support all the operations on the user-defined IDL interface, and the usual methods on object references.
The Java class is the client-side stub for the IDL interface. That Java class implements the IDL operations and the object reference methods and provides methods for object reference creation and object reference type checking. The Java class will be referred to as the stub Java class.
Methods on the object reference, e.g. operations from the IDL interface or general methods on IDL objects, are invoked on instances that support the object reference Java interface, while methods that operate on the client-side representation, e.g. object reference creation and type-checking, are invoked on the stub Java class.
This section explains the mapping from the user-defined IDL interface to the object reference Java interface and the stub Java class. The point of this section is to demonstrate how the operations from the IDL interface are kept separate from the other Java methods that are needed on IDL object references. However, this distinction won't be that important until we get to inheritance. (It's also an important distinction when we explain the server-side mapping.)
/* From Example.idl: */ module Example { interface Face { void method(); }; };
First we show the generated object reference Java interface, which is named by the name of the IDL interface, with the suffix ``Ref''.
// Generated to Example/FaceRef.java: package Example; public interface FaceRef extends omg.corba.ObjectRef, Example.FaceOperations { }
The object reference Java interface extends the omg.corba.ObjectRef
object reference Java interface, to get the declarations of the methods
available on all IDL object references. It also extends the
Example.FaceOperations interface, which gives the signatures of the
operations defined directly in the IDL interface. (That interface is not
shown because this section describes just the object reference Java
interface and the stub Java class.)
Stub Java class
Next we show the stub Java class for the same example IDL interface. The stub Java class is named with the name of the IDL interface, with the suffix ``Stub''.
/* From Example.idl: */ module Example { interface Face { }; };
// Generated to Example/FaceStub.java: package Example; public class FaceStub extends omg.corba.ObjectImpl implements Example.FaceRef { // IDL operations // Implementation of ::Example::Face::method public void method() throws omg.corba.SystemException { .... } // Type-specific CORBA::Object operations public omg.corba.ObjectRef duplicate() { .... } public static boolean isA(omg.corba.ObjectRef that) throws omg.corba.SystemException { .... } public static Example.FaceRef narrow(omg.corba.ObjectRef that) throws omg.corba.NarrowCoercionException, omg.corba.SystemException { .... } public static Example.FaceRef duplicate(Example.FaceRef that) { .... } }
The stub Java class implements the operations defined on (or inherited into) the IDL interface and any type-specific object reference methods. Some of the object reference methods are inherited from omg.corba.ObjectRef, and some are defined by each stub Java class. See Methods on CORBA::Object for details on what methods are available on all IDL object references.
Some of the object reference methods appear in two forms: a non-static method that operates on the object reference on which the method is invoked, and a static method that operates on an object reference passed as a parameter. The advantage of the static methods is that they can return a properly typed object reference. The advantage of the non-static methods is that they are more conveniently invoked on object reference instances.
The static duplicate
method takes a typed IDL object
reference as a parameter and returns a typed IDL object reference as a
result. The non-static duplicate
method duplicates the IDL
object reference and returns it widened to a omg.corba.ObjectRef (so
this non-static method can be overridden in each stub Java class). The
result can be cast (using the Java cast syntax) to the type of the
original object. (The corresponding non-static method to release an IDL
object reference (release
) is available on
omg.corba.ObjectRef.)
The static isA
method tests whether the IDL object
reference supplied as a parameter supports the IDL interface on whose
stub Java class the isA
method is called. (The
corresponding non-static method to check whether an IDL object reference
supports a particular IDL interface is available on
omg.corba.ObjectRef.)
The static narrow
method takes an IDL object reference
and returns a duplicate of that object reference typed as the IDL object
reference on whose stub Java class the narrow
method is
called.
The stub Java class has a private constructor, so clients can not create new IDL object references. A Java null can be passed anywhere a null object reference is needed.
The obvious name for the stub Java class is the unadorned name of the IDL interface. (One could argue that the unadorned name could instead be used for the object reference Java interface.) But because the scoping mechanism in Java is the Java package, and because IDL allows IDL declarations within IDL interface declarations, and because a Java class may not have the same name as the prefix of a Java package, the unadorned name is used for the Java package that contains any mappings of declarations within the IDL interface, and the stub Java class (and the object reference Java interface) have suffixes. This approach is justified because almost all Java identifiers are written with at least some package qualifier, and the hope is to minimize the number of suffixed that are needed.
If Java has contravariant return types, then the object-reference returning object reference methods could be declared on the object reference Java interface (and over-ridden by each extending object reference Java interface, and implemented by each implementing stub Java class). That would allow a user to say, for example:
Example.FaceRef original = ....; Example.FaceRef copy = original.duplicate(); // contravariant method
But since Java does not have contravariant return types, the type-specific object reference methods must either return a common type, or be static methods taking the object reference as a parameter. In reality, one must either cast the generic return, as in:
or avoid the Java cast by calling the static method, as in:Example.FaceRef original = ....; Example.FaceRef copy = (Example.FaceRef) original.duplicate();
Example.FaceRef original = ....; Example.FaceRef copy = Example.FaceStub.duplicate(original);
Since Java static methods can be invoked on instances as well as by giving the name of the Java class, one can also say:
Example.FaceRef original = ....; Example.FaceRef copy = original.duplicate(original);
An IDL interface can inherit from other IDL interfaces, meaning that the operations defined on the inherited IDL interfaces are also defined on the inheriting IDL interface. (Such inheritance is interface inheritance. Nothing is implied about the inheritance of implementations of the inherited operations.) The inherited interfaces are called base interfaces, and the inheriting interface is called the derived interface.) The derived IDL interface can also define additional operations that are not available on any of its base interfaces.
An IDL interface may inherit from more than one base IDL interface (multiple inheritance), and an IDL interface may be inherited more than once in an inheritance lattice. References to derived interfaces may be used anywhere a reference to a base interface is needed (implicit widening). References that have been widened to base interfaces may be narrowed to any interface in the inheritance lattice of the object, at the cost of an explicit runtime check.
We have seen above the basic object reference Java interface for an IDL interface with no inheritance. To model IDL multiple interface inheritance in Java we use multiple object reference Java interfaces in which the object reference Java interfaces for the more derived IDL interfaces extend the object reference Java interfaces for the more base IDL interfaces.
Here we show the standard diamond multiple inheritance lattice:
/* From Example.idl: */ module Example { interface Base { }; interface Left: Base { }; interface Right: Base { }; interface Derived: Left, Right { }; };
or, graphically:
Example::Base / \ / \ / \ / \ / \ Example::Left Example::Right \ / \ / \ / \ / \ / Example::Derived
and its mapping to Java:
// Generated to Example/BaseRef.java: package Example; public interface BaseRef extends omg.corba.ObjectRef, Example.BaseOperations { }
// Generated to Example/LeftRef.java: package Example; public interface LeftRef extends omg.corba.ObjectRef, Example.BaseRef, Example.LeftOperations { }
// Generated to Example/RightRef.java: package Example; public interface RightRef extends omg.corba.ObjectRef, Example.BaseRef, Example.RightOperations { }
// Generated to Example/DerivedRef.java: package Example; public interface DerivedRef extends omg.corba.ObjectRef, Example.LeftRef, Example.RightRef, Example.DerivedOperations { }
One can see from the above four generated object reference Java
interfaces how the IDL inheritance lattice is modeled in Java. Each
object reference Java interface extends the object reference Java
interfaces for any base IDL interfaces, and also extends the operations
Java interface for the IDL interface.
Operations on IDL interfaces
Operations on IDL interfaces are mapped as operations declared on the operations Java interface, which is extended by the object reference Java interface, and implemented by the stub Java class. The operations Java interface is named with the name of the IDL interface and the suffix ``Operations''. The operations Java interface is not used directly by client code, so the name of the operations Java interface need not be known to users.
/* From Example.idl: */ module Example { typedef .... ResultType; typedef .... ParameterType; exception That { .... }; interface Service { ResultType operation(in ParameterType arg) raises (That); }; };
// Generated to Example/ServiceOperations.java: package Example; public interface ServiceOperations { Example.ResultType operation(Example.ParameterType arg) throws omg.corba.SystemException, Example.That; }
// Generated to Example/ServiceRef.java: package Example; public interface ServiceRef extends omg.corba.ObjectRef, Example.ServiceOperations { }
// Generated to Example/ServiceStub.java: package Example; public class ServiceStub extends omg.corba.ObjectImpl implements Example.ServiceRef { // IDL operations // Implementation of ::Example::Service::operation public Example.ResultType operation(Example.ParameterType arg) throws omg.corba.SystemException, Example.That { .... }
The stub Java class implements the methods from the object reference Java interface. In the usual case, the stub Java class methods will be stubs for remote method invocations.
The operations were separated from the object reference so that
they could be inherited separately from the object reference
inheritance. The collection of operations is re-used, for example, on
the server-side to define the interface that a server must
support. The mapping of operations is straightforward, except for the
additional declaration that all IDL operations can throw
omg.corba.SystemException in addition to any user-defined exceptions
the IDL operation is declared to raise.
Parameter passing modes
IDL defines three parameter passing modes: in
,
out
, and inout
. The semantics of
in
is pass-by-value: the client supplies a value which is
not changed by the server (nor can the client modify the value during
the call). The semantics of out
is pass-by-result: the
server supplies a value which is not visible to the client until the
invocation returns. The semantics of inout
is
pass-by-value-result: the client supplies an actual parameter which is
changed by the server only when the invocation returns. Java has only
pass-by-value semantics, which only matches what is needed for IDL
in
simple and object reference parameters and results. In
the mapping, IDL in
parameters are supplied by supplying
the actual parameter to a call. Similarly, the results of IDL operations
are returned as the results of the corresponding Java method.
For IDL out
and inout
parameters, some
additional mechanism is necessary to support call-by-result and
call-by-value-result. The mapping defines holder Java classes for all
the IDL basic and user-defined types, and a client supplies an instance
of the appropriate holder Java class that is passed (by value) for each
IDL out
or inout
parameter. The contents of
the holder instance (but not the instance itself) is modified by the
invocation, and the client extracts the changed contents after the
invocation returns. Each holder class has a constructor from an instance
and a default constructor, and has a public instance member which is the
typed value. The holder Java classes for the IDL basic types are
available from the omg.corba Java package. For example, the
omg.corba.LongHolder class is:
package omg.corba; public class LongHolder { // Instance variable public int value; // Constructors public LongHolder() { this(0); } public LongHolder(int initial) { value = initial; } }
The IDL-to-Java compiler generates holder classes for all named user-defined types. Each generated holder Java class is named by the name of the IDL type for which it is the holder, with the suffix ``Holder''.
/* From Example.idl: */ module Example { typedef .... ResultType; typedef .... InType; typedef .... OutType; typedef .... InOutType; interface Modes { ResultType operation(in InType inArg, out OutType outArg, inout InOutType inoutArg); }; };
// Generated to Example/ModesOperations.java: package Example; public interface ModesOperations { Example.ResultType operation(Example.InType inArg, Example.OutTypeHolder outArg, Example.InOutTypeHolder inoutArg) throws omg.corba.SystemException; }
From the above one can see that the result comes back as an
ordinary result; that the in
parameter needs only an
ordinary value as the actual; but for the out
and
inout
parameters, an appropriate holder must be
constructed. A typical calling sequence for this operation might look
like:
Example.ModesRef target = .... // select a target object Example.InType inArg = .... // get the in actual Example.OutTypeHolder outHolder = // prepare to receive out new Example.OutTypeHolder(); Example.InOutTypeHolder inoutHolder = // set up in side of inout new Example.InOutTypeHolder(....); Example.ResultType result = // make the invocation target.operation(inArg, outHolder, inoutHolder); .... outHolder.value .... // use value of outHolder .... inoutHolder.value .... // use value of inoutHolder
Before the invocation, the in
side of the
inout
parameter must be put in the holder for the
inout
actual. The inout
holder can be filled
in either by constructing a new holder from a value, or by assigning
to the value of an existing holder of the appropriate type. After the
invocation, the client can use outHolder.value
to access
the value of the out
parameter, and
inoutHolder.value
to access the out
side of
the inout
parameter. The return result of the IDL
operation is available as the result of the invocation.
An alternative design was considered in which IDL operations with
out
(or inout
) parameters would return
instances of classes with public fields for the result of the
operation and the out
parameters. The nice thing about
this so-called multi-valued return style was that there was some
indication that multi-valued returns might eventually be added to the
Java language itself, in which case multi-valued returns would be the
``natural'' mapping for out
parameters. If the Java
language had multi-valued returns (using a made-up aggregate
assignment syntax ``{....} =''), the code could look like:
Example.ModesRef target = .... // select a target object Example.InType inArg = .... // get the in actual Example.OutType outArg; // prepare to receive out Example.InOutType inoutArg = .... // set up in side of inout Example.ResultType result; {outArg, inoutArg, result} = target.operation(inArg, inoutArg);
Before the invocation the client would have to set up the actual
parameters for the in
parameters and the in
sides of any inout
parameters. The invocation returns
multiple values, which the client assigns to the appropriate
variables. Note the clean separation between the in
parameters and the out
parameters, especially the
separation between the in
and out
sides of
inout
parameters.
The disadvantage of using the multi-valued return style when Java
does not have multi-valued returns is the extra code needed to
describe the classes for each multi-valued return type, and the added
complexity when an IDL operation evolves from a single-valued return
(an operation with a result, but no out
parameters) to a
multi-valued return (by the addition of out
or
inout
parameters). Using multi-valued return classes, the
above example would be:
Example.ModesRef target = .... // select a target object Example.InType inArg= .... // get the in actual Example.InOutType inoutArg = .... // set up in side of inout Example.ModesOperationMultiReturn result = // make the invocation target.operation(inArg, inoutArg); .... result.value .... // use result of operation .... result.outArg .... // use out parameter .... result.inoutArg .... // use out side of inout
Before the invocation, the client has to select the target object
reference, and compute the in
actual parameters and the
in
sides of inout
parameters. The invocation
returns an instance with instance variables for the return type of the
IDL operation and for each of the out
parameters (or the
out
sides of inout
parameters). The client
could then access result.value
,
result.outArg
, and result.inoutArg
to
extract the returned values. Changing an IDL operation to add an
out
parameter would thus complicate the client code for
all invocations, and require the construction of the result class
instance and the extraction of the values (even the result of the
operation) from the result class instance. Not to mention the
formation of the client-visible name for the class of the return type
of the operation, which has to be based on the name of the IDL
interface, and the name of the IDL operation.
Attributes
IDL interfaces can have attributes, which are syntactic sugar for accessor and modifier operations for typed fields. IDL attributes can be designated read-only, in which case only the accessor operation is defined; otherwise both an accessor operation and a modifier operation are defined. In the mapping, the name of the accessor method is the name of the attribute prefixed by ``get''. The name of the modifier method is the name of the attribute prefixed by ``set''.
/* From Example.idl: */ module Example { interface Attributes { attribute long Assignable; readonly attribute long Fetchable; }; };
// Generated to Example/AttributesOperations.java: package Example; public interface AttributesOperations { int getAssignable() throws omg.corba.SystemException; void setAssignable(int arg) throws omg.corba.SystemException; int getFetchable() throws omg.corba.SystemException; }
The corresponding implementations of those methods appear in the stub Java class.
Implementors write services. They make those services available via
object references. The mapping describes how an implementation instance
is made into an IDL object reference, and how invocations against the
implementation are delivered.
Being a servant
For each IDL interface the IDL-to-Java compiler generates an interface that defines the methods that any servant must implement to support the interface. This interface is called the servant Java interface. The servant Java interface is named by the name of the IDL interface with the suffix `Servant'. The servant Java interface is composed from servant Java interfaces of any inherited IDL interfaces and the operations defined directly by the IDL interface.
Using the previously defined IDL interface Derived:
/* From Example.idl: */ module Example { interface Derived: Left, Right { }; };
// Generated to Example/DerivedServant.java: package Example; public interface DerivedServant extends Example.LeftServant, Example.RightServant, Example.DerivedOperations { }
Here we can see the other use of the operations Java interface, to express the shared interface between the client-side object reference and the servant. The servant Java interface uses interface inheritance to inherit the methods required by any inherited IDL interfaces.
An implementation which wants to be a servant for an IDL interface declares itself to implement the servant Java interface. No other demands are made on an implementation. In particular, there is no requirement that an implementation extend any Java classes.
The methods declared in the operations Java interface could instead be declared in both the object reference Java interface and the servant Java interface, saving one Java interface per IDL interface, but then the correspondence between the object reference and any servants would be obscured.
The operations Java interface could express the inheritance
relationship among the corresponding IDL interfaces. If we did that, a
derived operations Java interface would say it extended all the
operation Java interfaces of directly inherited IDL interfaces, and
added any operations defined in the derived IDL interface. That might
let us get rid of the servant Java interface, which exists to declare
the inheritance, but is also a place where we can inherit in any methods
that are common to all servant (currently none). Having the inheritance
in the operations Java interface might confuse people since we also
express the IDL inheritance in the object reference Java interfaces on
the client side so implicit widening between object references works.
From servant to object reference
An implementation is written to support the methods from the servant Java interface. In addition, the implementation may provide its own constructors, post-construction initialization methods, and so on.
In response to a request from a client, the service creates an instance of the implementation object, calling constructors with appropriate arguments, recording the instance in the state of the service, or whatever set-up the service requires. Finally, an object reference is created from the implementation instance that can be passed to clients.
The method to create an object reference from a servant instance is defined on the skeleton Java class generated for each IDL interface. The skeleton Java class is named from the name of the IDL interface and the suffix `Skeleton'. The skeleton Java class provides two static methods to create object references. The simpler method just takes the servant instance as a parameter and returns an object reference that uses a default object adaptor. The two-argument method takes an additional object adaptor parameter and creates the object reference using that object adaptor.
Using the previously defined IDL interface Derived:
// Generated to Example/DerivedSkeleton.java: package Example; package Example; public class DerivedSkeleton implements omg.orb.Skeleton { // Public methods to create references from servers public static Example.DerivedRef createRef( Example.DerivedServant servant) { .... } public static Example.DerivedRef createRef( omg.orb.Server server, Example.DerivedServant servant) { .... }
An alternative server-side architecture has the implementation extending a servant Java class (not interface). Such an architecture has the advantage that then an implementation can inherit common implementations of methods from the skeleton Java class. The disadvantage is that Java allows each class to extend only a single other class, so this scarce resource is preempted for the implementation and can not be used by the implementation to inherit methods needed by the implementation.
Complex schemes were worked out to allow a servant to either inherit from a skeleton Java class or to allow a server writer to explicitly call an object reference creation method on the skeleton Java class (or both!). In the end we decided that a single mechanism for object reference creation was better than two mechanisms, and that explicitly turning an implementation into an object reference was better than using up the only available implementation inheritance.
The CORBA::Object type maps to the object reference Java interface omg.corba.ObjectRef and the stub Java class omg.corba.ObjectStub as shown below. The object reference Java interface omg.corba.ObjectRef is empty because CORBA::Object defines no ordinary methods, and inherits no IDL interfaces. The stub Java class omg.corba.ObjectStub implements the Java methods that manipulate CORBA::Object object references. The object reference Java interface for each user-defined IDL interface extends the object reference Java interface omg.corba.ObjectRef, so that any object reference Java interface can be passed anywhere an omg.corba.ObjectRef is expected.
package omg.corba; public interface ObjectOperations { } package omg.corba; public interface ObjectRef { // Public methods: // Check if this object supports the interface represented // by the argument. boolean isA(String repositoryIdentifier) throws omg.corba.SystemException; // Create a new object reference from this object reference. // The duplicate method can be called on any object reference, // and the result cast (or narrowed) to the type of the // original object reference. omg.corba.ObjectRef duplicate(); // Indicate that resources for this object reference // are no longer needed. void release(); } package omg.corba; public class ObjectStub extends omg.corba.ObjectImpl implements omg.corba.ObjectRef { // Type-specific object reference methods public omg.corba.ObjectRef duplicate() { .... } public static boolean isA(omg.corba.ObjectRef that) throws omg.corba.SystemException { .... } public static omg.corba.ObjectRef narrow(omg.corba.ObjectRef that) { .... } public static omg.corba.ObjectRef duplicate(omg.corba.ObjectRef that) throws omg.corba.SystemException { .... } }
The Java class omg.corba.ObjectImpl provides shared (that is, implementation inheritable) implementations fo some of the methods on object references:
package omg.corba; abstract public class ObjectImpl { // Shared implementations on type-neutral object reference methods. // Non-static type-neutral object reference methods. public boolean isA(java.lang.String repositoryIdentifier) throws omg.corba.SystemException { .... } public void release() { .... } // Static type-neutral object reference methods public static void release(omg.corba.ObjectRef that) { .... } }
The mappings for all IDL defined exceptions inherit from the Java class omg.corba.CORBAException. There are two Java subclasses of omg.corba.CORBAException, omg.corba.SystemException for standard system exceptions, and omg.corba.UserException for user-defined exceptions. This hierarchy allows one to catch exceptions by their category as well as by their particular type.
The Java class omg.corba.CORBAException provides the following declarations:
// From omg/corba/Exception.java: package omg.corba; public class CORBAException extends java.lang.Exception { // CORBA defined exception types. static public final int None = 0, User = 1, System = 2; // Constructor protected CORBAException(int type, String id) .... // Accessors public int exceptionType() .... public String exceptionId() .... }
Note that the constructor is protected, so that only specific subclasses can create instances of omg.corba.CORBAException. The exceptionId method returns the repository identifier for the exception.
The standard system exceptions are mapped to Java classes that allow one to access the minor code and completion status for the exception. All standard system exceptions are subclasses of omg.corba.SystemException, shown below:
// From omg.corba.SystemException.java: package omg.corba; public class SystemException extends omg.corba.CORBAException { // Completion status constants. static public final int CompletedYes = 0, CompletedNo = 1, CompletedMaybe = 2; // Accessors public int getCompleted() { .... } public void setCompleted(int completed) { .... } public int getMinorCode() { .... } public void setMinorCode(int minorCode) { .... } }
Note that there are no public constructors for omg.corba.SystemException; only classes that extend omg.corba.SystemException can be instantiated.
The standard IDL system exceptions are mapped to Java classes that extend omg.corba.SystemException. As an example, omg.corba.UnknownException is declared to include:
// From omg/corba/UnknownException.java: package omg.corba; public class UnknownException extends omg.corba.SystemException { public UnknownException() .... public UnknownException(int minor, int completed) .... }
The default constructor supplies some default values for the minor code and completion status. The constructor from elements allows one to supply those fields (and supplies the appropriate exception category and exception identifier to the constructor for omg.corba.CORBAException. The standard system exceptions are mapped to the Java classes:
omg.corba.BadContextException omg.corba.BadInvocationOrderException omg.corba.BadOperationException omg.corba.BadParameterException omg.corba.BadTypeCodeException omg.corba.CommunicationFailureException omg.corba.DataConversionException omg.corba.ImplementationLimitException omg.corba.InterfaceRepositoryException omg.corba.InternalException omg.corba.InvalidFlagException omg.corba.InvalidIdentiferException omg.corba.InvalidObjRefException omg.corba.MarshalingException omg.corba.MemoryAllocationException omg.corba.MemoryDeallocationException omg.corba.NoImplementationException omg.corba.NonexistentObjectException omg.corba.NoPermissionException omg.corba.NoResourcesException omg.corba.NoResponseException omg.corba.ObjectAdapterException omg.corba.OrbInitializationException omg.corba.PersistentStorageException omg.corba.TransientException omg.corba.UnknownException
The system exceptions could have been mapped using the names declared in the corba module of the IDL specification. That would have resulted in names that were ``unnatural'' for Java programmers. Instead, we made up ``natural'' Java names for the system exceptions.
Copyright ©
1996 Sun Microsystems, Inc., 2550 Garcia Ave., Mtn. View, CA 94043-1100 USA.
Talk with Java IDL users via the mailing list idl-users@java.sun.com.
Send questions or comments about this site to webmaster@wombat.eng.sun.com.
|
|