User-Defined Data Types (Java Enterprise in a Nutshell) Book Home Java Enterprise in a Nutshell Search this book

10.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 you define yourself using structs and unions. We'll go over each in detail in this section.

A complex data type is used in IDL by first giving it a type name, then using the type name wherever you would use a basic data- or interface-type name (e.g., declaring attributes, method arguments). There are a few ways a name is assigned to a complex data type:

Before we go on to see how complex data types are declared in IDL, let's take a look at how a typedef assigns a type name to a complex data type.

10.7.1. Typedefs

A typedef associates a name with another data type. Here is the syntax of an IDL typedef:

typedef type identifier

The type 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.

10.7.1.1. Mapping typedefs to Java

If an IDL typedef refers to a basic IDL type, the Java equivalent to that type is used wherever the typedef identifier is used. So our myShorttypedef 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, its target type is found, and so on, until a final user-defined type or basic IDL type is found. Consider 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 they both refer (directly or indirectly) to that type.

10.7.2. Arrays

Arrays can only be declared 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. IDL doesn't provide a way to initialize array values, so you cannot 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 explicitly specify each dimension of the array, in order to support mappings to languages that have a similar requirement.

10.7.2.1. Mapping arrays to Java

Arrays are mapped into Java as arrays (naturally). So, if we use the short2x2Array type defined above in an IDL interface:

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

the corresponding Java code looks 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 to a short array in Java. The attribute is mapped to get() and set() methods for that attribute. Since Java doesn't allow array type specifiers to include dimensions, our declaration that the identity2D attribute be a 2-by-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 mapping 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 short2x2ArrayHelper and short2x2ArrayHolder classes 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 marshalled, 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 of helper and holder classes, see Chapter 4, "Java IDL".

10.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. Note 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.

10.7.3.1. Mapping sequences to Java

Sequences are mapped to Java almost identically to arrays. A sequence of a given IDL type becomes a Java array of the equivalent Java type, sequences of sequences become two-dimensional arrays, etc. A holder and helper class 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 you specify on the sequence, throwing an org.omg.CORBA.MARSHAL exception if they don't match.

10.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 specification and an identifier that references the member (similar to attributes on an interface, described in Section 10.10.1, "Attributes" in Section 10.10, "Interface Declarations"). 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;
};

10.7.4.1. Mapping structs to Java

An IDL struct is mapped to 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 example struct above 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 marshalls the data type when it's used as an inout or out method argument or as a method return value.

10.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 enumeration 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 recognize the order of the elements as specified in the enum declaration. In other words, the first element is less than the second, the second is less than the third, etc. An example enum declaration follows:

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

10.7.5.1. Mapping enumerations to Java

Each enumerated type you declare in IDL is mapped to a publicfinal Java class of the same name as the enumeration. The class holds a single privateint instance member called value. A single private constructor is generated for the class, which takes an int argument that initializes 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 has to use the object versions of the elements.

Our example enumeration generates a Java class like the following:

// Java
public final class ErrorCode {
	public static final int _BadValue = 0,
		_DimensionError = 1,
		_Overflow = 2,
		_Underflow = 3;
	public static final ErrorCode BadValue = new ErrorCode(_BadValue);
	public static final ErrorCode DimensionError = new 
      ErrorCode(_DimensionError);
	public static final ErrorCode Overflow = new ErrorCode(_Overflow);
	public static final ErrorCode Underflow = new ErrorCode(_Underflow);
	public int value() {
		return _value;
	}
	public static final ErrorCode from_int(int i) throws 
      org.omg.CORBA.BAD_PARAM {
		switch (i) {
			case _BadValue:
				return BadValue;
			case _DimensionError:
				return DimensionError;
			case _Overflow:
				return Overflow;
			case _Underflow:
				return Underflow;
			default:
				throw new org.omg.CORBA.BAD_PARAM();
		}
	}
	private ErrorCode(int _value){
		this._value = _value;
	}
	private int _value;
}

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 that 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 defined by the OMG, an enumerated type might also have a helper class generated for it.

10.7.6. Unions

IDL unions are similar in nature to discriminated unions in C and C++. A single tag field, or discriminator, determines 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 (string, struct, union, array, and sequence are not 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. Consider the following union:

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

This declares a type named MultiCoord that represents a one-, two-, or three-dimensional coordinate, depending on the value of its discriminator value. The default is for the coordinate to be three-dimensional, 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, we 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.

10.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 are not 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 you specify in the IDL union is mapped to an accessor method and modifier method for that branch, and 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, 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, an org.omg.CORBA.BAD_OPERATION exception 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 {
	// instance variables
	private boolean __initialized;
	private short __discriminator;
	private java.lang.Object __value;
	private short _default = 4;
	// constructor
	public MultiCoord() {
		__initialized = false;
		__value = null;
	}
	// discriminator accessor
	public short discriminator() throws org.omg.CORBA.BAD_OPERATION {
		if (!__initialized) {
			throw new org.omg.CORBA.BAD_OPERATION();
		}
		return __discriminator;
	}
	// branch constructors and get and set accessors
	public int pos() throws org.omg.CORBA.BAD_OPERATION {
		if (!__initialized) {
			throw new org.omg.CORBA.BAD_OPERATION();
		}
		switch (__discriminator) {
			case (short) (1L):
				break;
			default:
				throw new org.omg.CORBA.BAD_OPERATION();
		}
		return ((org.omg.CORBA.IntHolder) __value).value;
	}
	public void pos(int value) {
		__initialized = true;
		__discriminator = (short) (1L);
		__value = new org.omg.CORBA.IntHolder(value);
	}
	public int[] val2d() throws org.omg.CORBA.BAD_OPERATION {
		if (!__initialized) {
			throw new org.omg.CORBA.BAD_OPERATION();
		}
		switch (__discriminator) {
			case (short) (2L):
				break;
			default:
				throw new org.omg.CORBA.BAD_OPERATION();
		}
		return (int[]) __value;
	}
	public void val2d(int[] value) {
		__initialized = true;
		__discriminator = (short) (2L);
		__value = value;
	}
	public int[] val3d() throws org.omg.CORBA.BAD_OPERATION {
		if (!__initialized) {
			throw new org.omg.CORBA.BAD_OPERATION();
		}
		switch (__discriminator) {
			default:
				break;
			case (short) (1L):
			case (short) (2L):
				throw new org.omg.CORBA.BAD_OPERATION();
		}
		return (int[]) __value;
	}
	public void val3d(int[] value) {
		__initialized = true;
		__discriminator = (short) (3L);
		__value = value;
	}
}

Notice that Sun's idltojava compiler implements the data branches in the union using a single java.lang.Object data member, which references an object of the appropriate type when the union is put into a particular branch.

In this case, 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), an accessor and modifier method are generated for its branch as well. If two explicit cases are mapped to the same branch in the switch, 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 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(int discrim, 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.[4]

[4]The current version of Sun's idltojava compiler, which originally shipped with the beta 2 of JDK 1.2, violates this part of the standard and does not generate the extra modifier method for multicase branches.

If no explicit default case is given in the union and if the listed cases do not completely cover the possible values for the discriminator, 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 class also has a holder class generated for it. Although not specified in the standard mapping, it might also have a helper class generated, but you shouldn't depend on the helper class being present in the generated Java.



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.