Section G.7. User-Defined Data Types


G.7. User-Defined Data Types

In addition to the basic data types already described, IDL supports user-defined data types, which are aggregations of these basic types. These complex data types include arrays, sequences, enumerations, and constructed data types that you define yourself using structs and unions. We'll go over each of these in detail in this section.

Complex data types are used in IDL by first giving them a type name, then using the type name wherever you would use a basic data type name or an interface type name (e.g., declaring attributes, method arguments, etc.). A name can be assigned to a complex data type in different ways:

  • With structures, unions, and enumerations, the name is included in the declaration of the data type.

  • A typedef can be used to assign a name to a specific type (basic or complex).

Before we go on to see how complex data types are declared in IDL, let's take a look at how typedefs are used to assign type names to these complex data types.

G.7.1. Typedefs

A typedef is used to associate a name with another data type. The syntax of an IDL typedef is:

 typedef <type_spec> <identifier> 

The <type_spec> can be any basic IDL data type, a user-defined data structure (structure, union, or enumeration), an IDL interface type, or a sequence. The <identifier> can be a simple IDL identifier, or it can include dimension specifications for an array. So the following are all valid typedef statements:

 // IDL  typedef short myShort; typedef long longArray[2][2]; typedef PrintServer pserver; 

After declaring these typedefs in your IDL file, you can use myShort, longArray, and pserver as type names when declaring method arguments, return values, or interface attributes.

G.7.1.1. Mapping typedefs to Java

If an IDL typedef refers to a basic IDL type, then the Java equivalent to that type is used wherever the typedef identifier is used. So our myShort typedef shown in the previous section is replaced by the Java type short wherever it's used.

Any typedefs that refer to user-defined types are replaced by the mapped Java class or interface for the target IDL type. If the type used in an IDL typedef is itself a typedef, then its target type is found and so on until a final user-defined type or basic IDL type is found. So in this example:

 // IDL struct LinkedList {      any item;      any next; }; typedef LinkedList DefList; typedef DefList MyList; 

wherever either DefList or MyList appears in the IDL file, it is mapped to the Java class generated for the LinkedList type, since both refer (directly or indirectly) to that type.

A helper class is also generated for each typedef, providing the usual helper methods in terms of the underlying mapped type. So a myShortHelper class is generated from our example, but its methods are declared in terms of the underlying short type (e.g., its read( ) method returns a short). If the underlying type is a sequence or array, then a holder class is also generated for the typedef.

G.7.2. Arrays

Arrays can be declared only within the context of a typedef. Once you've assigned the array type to a type name using the typedef, you can use the new type name to declare array members on interfaces. Since IDL doesn't provide a way to initialize array values, you can't declare array constants in IDL, since constants have to be initialized in their declaration.

To declare an array, simply add dimensions in brackets to a variable identifier. For example, to define a two-dimensional array of short values:

 // IDL typedef short short2x2Array[2][2]; 

IDL requires that you specify explicitly each dimension of the array, in order to support mappings to languages that have a similar requirement.

G.7.2.1. Mapping arrays to Java

Arrays are mapped into Java as arrays (naturally). So if we used our short2x2Array type defined earlier in an IDL interface:

 // IDL interface MatrixOps {      attribute short2x2Array identity2D;      ... 

the corresponding Java code would look like so:

 // Java public interface MatrixOps {      short[][] identity2D(  );      void identity2D(short[][] arg);      ... 

We'll look more at how interface attributes are mapped to Java later, but you can infer from this that the short IDL array is mapped into a short array in Java. The attribute is mapped into a get and set method for that attribute, and since Java doesn't allow array type specifiers to include dimensions, our declaration that the identity2D attribute be a 2 x 2 array has been lost in the mapping. It's up to you to provide an implementation of this interface that enforces the intended dimensions of the array within the Java interface.

In addition to the mapping of the array type to equivalent type specifiers, each array typedef in IDL causes the generation of corresponding helper and holder classes in Java. The type name specified in the IDL typedef is used as the prefix for the xxxHelper and xxxHolder class names. So our short2x2Array type has a short2x2ArrayHelper class and a short2x2ArrayHolder class generated for it. The helper class provides the static methods that read and write the array type over CORBA I/O streams, when the array type is used as a method argument or return type. These methods enforce the array dimensions that you dictate in your IDL typedef; if the array is not of the correct type when being marshaled, then the write( ) method throws an org.omg.CORBA.MARSHAL exception. The holder class is used whenever you use your array type as an inout or out method argument. For more details on the purposes on helper and holder classes, see Chapter 14.

G.7.3. Sequences

An IDL sequence is a one-dimensional array. To declare a sequence, you need to declare the type of the elements in the sequence and, optionally, the maximum size of the sequence:

 // IDL typedef sequence<long, 2> longVector; typedef sequence<short> unboundedShortVector; typedef sequence<sequence<float, 2> > coordVector; 

Like arrays, sequences have to be declared within a typedef, and then the new type name can be used for typing attributes, method arguments, and return values. Notice in our last example that the elements in a sequence can themselves be a sequence. Also notice that, if you don't provide a bound for a sequence of sequences, you need to put a space between the two > brackets, so that they aren't parsed as a >> operator.

G.7.3.1. Mapping sequences to Java

Sequences are mapped to Java almost identically to how arrays are. A sequence of a given IDL type becomes a Java array of the equivalent Java type. Sequences of sequences become two-dimensional arrays, and so forth. Holder and helper classes are generated for each sequence typedef as well, using the type name specified in the typedef. The write( ) method on the helper class enforces any size bounds that you specify on the sequence, throwing an org.omg.CORBA.MARSHAL exception if they don't match.

G.7.4. Structs

A fixed data structure is declared using the struct construct in IDL. A struct is declared using the following syntax:

 // IDL struct <type name> {      <data member>;      <data member>;      ... }; 

The type name is any valid identifier in IDL. Each data member is specified using a type spec and an identifier used to reference the member (similar to attributes on an interface, described later). You can use basic data types, arrays, sequences, and any other typedefs as types for members of a struct. You can declare a recursive structure (a structure that includes members of its own type) by using a sequence declaration:

 // IDL struct LispStringList {      string car;      sequence<LispStringList> cdr; }; 

G.7.4.1. Mapping structs to Java

An IDL struct is mapped into a public final Java class with the same name as the struct. Each member of the struct is mapped to a public instance member on the Java class. The Java class includes a default constructor that leaves the member variables uninitialized and a constructor that accepts a value for each member. Our previous example struct is mapped to the following Java class:

 // Java public final class LispStringList {      // Instance variables      public String car;      public LispStringList[] cdr;      // Constructors      public LispStringList(  ) { }      public LispStringList(String _car, LispStringList[] _cdr) {           car = _car;           cdr = _cdr;      } } 

Each struct also has a Java holder class generated for it, which is used to marshal the data type when it's used as an inout or out method argument or as a method return value.

G.7.5. Enumerations

An enumeration in IDL declares an ordered list of identifiers, whose values are assigned in ascending order according to their order in the enumeration. An enumerator is given a type name so that the elements of the enumeration can be referenced. The syntax for declaring an IDL enumeration is:

 // IDL enum <type name> { <element name>, <element name>, ... }; 

The elements in the enumeration are guaranteed to be assigned actual values so that the comparison operators in the implementation language will recognize the order of the elements as specified in the enum declaration. So the first element is less than the second, the second is less than the third, and so on. An example enum declaration follows:

 // IDL enum ErrorCode { BadValue, DimensionError, Overflow, Underflow }; 

G.7.5.1. Mapping enumerations to Java

Each enumerated type that you declare in IDL is mapped to a public final Java class of the same name as the enumeration. The class holds a single private int instance member. A single private constructor is generated for the class, which takes an int argument used to initialize the value member.

For each element of the enumeration, two components are added to the Java class: a static final int data member and a static instance of the generated Java class. The static data member generated for each element is given a value that enforces the order of the elements in the enumeration, and the static class instance generated for each element is initialized with this same value. The static class instance is given the same name as the element in the enumeration, and the static data member is given the element's name prepended with an underscore. These two representations for each element of the enumeration let you reference the element value using either a corresponding int value or the generated Java class type. If the enumerated type is used as a method argument or return value in an IDL interface, your Java implementation will have to use the object versions of the elements.

Our example enumeration generates a Java class like the following:

 // Java public class ErrorCode implements org.omg.CORBA.portable.IDLEntity {   private        int _value;   private static int _size = 4;   private static ErrorCode[] _array = new ErrorCode [_size];      public static final int _BadValue = 0;   public static final ErrorCode BadValue = new ErrorCode(_BadValue);   public static final int _DimensionError = 1;   public static final ErrorCode DimensionError =     new ErrorCode(_DimensionError);   public static final int _Overflow = 2;   public static final ErrorCode Overflow = new ErrorCode(_Overflow);   public static final int _Underflow = 3;   public static final ErrorCode Underflow = new ErrorCode(_Underflow);     public int value (  )   {     return _value;   }     public static ErrorCode from_int (int value)   {     if (value >= 0 && value < _size)       return _array[value];     else       throw new org.omg.CORBA.BAD_PARAM (  );   }     protected ErrorCode (int value)   {     _value = value;     _array[_value] = this;   } } 

So we can refer to the elements in the enumeration in our Java code using any of the following forms:

 // Java int error1 = ErrorCode._BadValue; ErrorCode error2 = ErrorCode.Overflow; int error2Val = error2.value(  ); 

Each enumerated type also has a holder class generated for it, which is used whenever the enumerated type is used in IDL as an out or inout method argument. Although not strictly required by the IDL-Java mapping as defined by the OMG, an enumerated type might also have a helper class generated for it.

G.7.6. Unions

IDL unions are similar in nature to discriminated unions in C and C++. A single tag field, or discriminator, is used to determine the data element held by the union. Depending on the value of the discriminator field, a particular instance of the union type may hold a different data member. The union is declared using a switch statement to declare the various possible formats, or branches, of the union structure:

 // IDL union <type name> switch (<discriminator type>) {      case <tag value>:           [<data element>;]      case <tag value>:           [<data element>;]      ...      [default:]           <data element>; }; 

The discriminator for the union is declared using only the type for the discriminator (no identifier is given to the discriminator, since there is only a single discriminator per union type). The type for the discriminator must be an integer, character, Boolean, or enumerated type (no strings, structs, unions, arrays, or sequences allowed).

Each branch in the switch defines a data element that represents the value of the union if its discriminator is a given value. Each data member identifier in a union switch has to be unique. Multiple cases can be mapped to the same data element by listing them sequentially within the switch. A single optional default case can be given for any values not given their own cases. So for example, the following union:

 // IDL typedef sequence<long, 2> Coord2d; typedef sequence<long, 3> Coord3d; union MultiCoord switch (short) {      case 1:           long pos;      case 2:           Coord2d val2d;      case 3:      default:           Coord3d val3d; }; 

declares a type named MultiCoord, which represents a one-, two-, or three-dimensional coordinate, depending on the value of its discriminator value. The default is for the coordinate to be 3-D, so the case for a discriminator value of 3 is the same as the default case. Since a union can have only a single data member per case, you have to use typedef types for the coordinate values. Depending on the discriminator value, the union contains either a simple integer position, a Coord2D type that is declared as a sequence of two integer values, or a Coord3D type that is a sequence of three integer values.

If the discriminator value is given a value not listed in a case, the union consists of the data member in the default case, if present. If there is no default case, the union has only its discriminator value and no data members.

G.7.6.1. Mapping unions to Java

Each IDL union is mapped to a public final Java class of the same name as the union identifier. The class contains a single, default constructor. The class has some kind of data member for maintaining the value of the union discriminator (the details of which aren't dictated by the IDL-to-Java mapping) and a discriminator( ) method for accessing it as a short value. The standard also doesn't specify how data members for the union are implemented in the Java class. Each branch that you specify in the IDL union is mapped to an accessor method and modifier method for that branch; these methods are named after the identifier given to the data member in the branch. If you use one of the modifier methods to set that branch of the union type, then the discriminator is automatically set to the corresponding value. If you attempt to access the value from a branch and the union is not set to that branch, then a CORBA::BAD_OPERATION exception (org.omg.CORBA.BAD_OPERATION in Java) is thrown. The return value types and method arguments for the discriminator( ) method and the case accessor/modifier methods are determined based on the standard type conversion rules for mapping IDL to Java.

Our MultiCoord union example is mapped to the following Java class by Sun's idltojava compiler:

 // Java public final class MultiCoord implements org.omg.CORBA.portable.IDLEntity {   private int _pos;   private int[] _val2d;   private int[] _val3d;   private short _discriminator;   private boolean _uninitialized = true;     public MultiCoord (  )   {   }     public short discriminator (  )   {     if (_uninitialized)       throw new org.omg.CORBA.BAD_OPERATION (  );     return _discriminator;   }   public int pos (  )   {     if (_uninitialized)       throw new org.omg.CORBA.BAD_OPERATION (  );     verifypos (_discriminator);     return _pos;   }     public void pos (int value)   {     _discriminator = 1;     _pos = value;     _uninitialized = false;   }     private void verifypos (short discriminator)   {     if (discriminator != 1)       throw new org.omg.CORBA.BAD_OPERATION (  );   }     public int[] val2d (  )   {     if (_uninitialized)       throw new org.omg.CORBA.BAD_OPERATION (  );     verifyval2d (_discriminator);     return _val2d;   }     public void val2d (int[] value)   {     _discriminator = 2;     _val2d = value;     _uninitialized = false;   }     private void verifyval2d (short discriminator)   {     if (discriminator != 2)       throw new org.omg.CORBA.BAD_OPERATION (  );   }     public int[] val3d (  )   {     if (_uninitialized)       throw new org.omg.CORBA.BAD_OPERATION (  );     verifyval3d (_discriminator);     return _val3d;   }     public void val3d (int[] value)   {     _discriminator = 3;     _val3d = value;     _uninitialized = false;   }     public void val3d (short discriminator, int[] value)   {     verifyval3d (discriminator);     _discriminator = discriminator;     _val3d = value;     _uninitialized = false;   }     private void verifyval3d (short discriminator)   {     if (discriminator == 1 || discriminator == 2)       throw new org.omg.CORBA.BAD_OPERATION (  );   } } 

Notice that Sun's idlj compiler implements the data branches in the union using a set of data members, one for each branch of the union. Other IDL -to-Java compilers can choose to implement the union differently.

In this example, the default case and the third case share the same branch, so no accessor or modifier method is generated for the default case. If we have a default case that is separate from all other explicit cases in the union (i.e., has its own branch), then an accessor and modifier method is generated for its branch as well. If two explicit cases are mapped to the same branch in the switch, then the Java modifier method generated for that branch sets the discriminator value to the value of the first case included for that branch. In these cases, another modifier method, which takes a second argument that is the value for the discriminator, is also generated. As an example, if we want to use a Coord2D for both 1D and 2D coordinates, we can modify our IDL union to have both case 1 and case 2 use the same branch:

 typedef sequence<long, 2> Coord2d; typedef sequence<long, 3> Coord3d; union MultiCoord switch (short) {  case 1:  case 2:    Coord2d val2d;  case 3:    Coord3d val3d;  default:    Coord3d valDef; }; 

In this situation, the generated Java has an additional method included for the val2d branch:

 public void val2d (short discriminator, int[] value) { ... } 

This allows you to set the union to that branch and also specify which discriminator is intended. This can be useful in some cases, such as our modified MultiCoord example, where the value of the discriminator determines the usage for the object.

If no explicit default case is given in the union and if the listed cases don't completely cover the possible values for the discriminator, then the generated Java class includes a single method named default( ) that takes no arguments and returns a void. This serves as the modifier for the default case, setting the union discriminator to some unused value.

The union also has a holder and helper class generated for it in the mapped Java.



Java Enterprise in a Nutshell
Java Enterprise in a Nutshell (In a Nutshell (OReilly))
ISBN: 0596101422
EAN: 2147483647
Year: 2004
Pages: 269

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net