VisiBroker for .NET Developer’s Guide : Using hints and custom marshaling

Using hints and custom marshaling
This chapter explains how to use hints to control java2cs code generation for valuetypes in VisiBroker for .NET.
VisiBroker for .NET has a powerful mechanism that lets the user customize the code generation for Java valuetypes. Valuetypes are value classes that are implemented in Java (typically extending java.io.Serializable directly or indirectly). These classes have state and are intended to be marshaled over the wire as state.
VisiBroker for .NET code generation—an example
In order to fully understand the use of hints and how they affect java2cs code generation, the following example shows a simple Java type called User.
public class User implements java.io.Serializable {
public String name;
private String password;
public User (String name, String password) {
this.name = name;
this.password = password;
}
}
Obviously this example class is not realistic as it does not allow access to initialize or in any way use the private state of this object. However, we are skipping a real implementation of this object (with appropriate constructors and methods) for the sake of simplicity. For this discussion methods in Java classes are irrelevant.
Note:
We are generating a C# class corresponding to the Java class. The methods in Java classes are irrelevant because porting the methods would involve essentially reverse engineering the Java class, and so the porting of methods is not supported. If you would like to have the same or similar methods in your C# class corresponding to the Java version of your valuetype, you will have to implement the C# equivalent yourself. Later sections in this document will explain how that is done.
The important sections of the C# code that is generated from the example Java class, User, are shown below.
[System.Serializable] public class User {
private string _Name;
public virtual string Name {
get { return this._Name; }
set { this._Name = value; }
}
private string _Password;
public virtual string Password {
get { return this._Password; }
set { this._Password = value; }
}
// Other common object methods omitted
}
The C# type User represents the Java class User. As is apparent, this is incorrect in a few ways.
It provides public accessors to the private field (password, _Password in C#). This will happen regardless of whether the Java type provides the same accessors or not. As mentioned, the compiler will not look at the Java methods.
This class demotes the access modifier of the field name (_Name in C#) from public to private, but a public property is provided for access.
In short, this class is not very usable. However, it provides you a starting point from which you can build your real valuetype. You can cut this code from the generated code, add it to your source, and add all the useful constructors and methods. We will show you later how to avoid generating this class again, and instead use your version.
ValueFactory class
Now let us look at the generated ValueFactory class for User. This class is responsible for creating and initializing an instance of the C# type User when it reads an instance of the Java class User from the network. it is also responsible for writing the correct data to the network when you pass an instance of the C# class User to a remote server. It is important to note that the ValueFactory is associated with the corresponding Java type. That is, each distinct Java type will have a distinct factory. This allows more than one Java type to map to a given C# type.
ValueFactory methods
The ValueFactory class has many methods, but the following example highlights the most significant ones that you will need to know.
public class UserValueFactory : CORBA.ValueFactory {
public virtual CORBA.TypeCode GetTypeCode() {
return UserHelper.GetTypeCode();
}
public virtual System.Type GetValueType() {
return typeof(User);
}
public virtual User CreateObject() {
return new User();
}
public virtual void InitObject(UserValueData src_data, User dst_object) {
dst_object.Name = src_data.Name;
dst_object.Password = src_data.Password;
}
public virtual void InitData(User src_object, UserValueData dst_data) {
dst_data.Name = src_object.Name;
dst_data.Password = src_object.Password;
}
}
Note that UserValueData is a class that contains as public data members every instance member of the User class as shown in the following example.
public class UserValueData {
public string Name;
public string Password;
}
The following table describes the ValueFactory methods:
Used when reading a Java MyValue. The C# type created by CreateObject is passed to it as well as the ValueData class. When the call to InitObject is made, the data for MyValue has already been unmarshaled into the ValueData class. The InitObject merely assigns the fields from the ValueData class to the C# MyValue class. We will see the usefulness of this pattern later.
Based on the above table, you see that the ValueData class represents the data that is marshaled on the wire, irrespective of how the data is stored or maintained in the C# type.
Notice that the ValueFactory created the object in one step (CreateObject) and read the data in another step (InitObject). There is a good reason for this. When unmarshaling or marshaling a type that is inherited from other stateful types, each type's factory is normally responsible for marshaling and unmarshaling only the state at its level in the hierarchy. To achieve this, the infrastructure will first instantiate an instance of the type that is being unmarshaled, but will pass it to the factory corresponding to each type in the hierarchy, starting from the base, to unmarshal the relevant state and work its way up the hierarchy. When writing, the same process is repeated, this time using the InitData methods.
An introduction to hints
The hints file is an XML file that provides hints to the java2cs compiler allowing the user to customize the code that is generated.
The following is an example of a simple hints.xml file.
<?xml version=”1.0” ?>
<hints>
<hint>
<java-class>User</java-class>
<cs-impl-type>UserData</cs-impl-type>
</hint>
</hints>
To run the java2cs compiler with the above hints file, enter the following at the command line:
java2cs -hint_file hints.xml -o User.cs User
Supplying the implementation of a value type
Running the compiler with the following hint will cause the compiler to stop generating the User class.
<?xml version=”1.0” ?>
<hints>
<hint>
<java-class>User</java-class>
<cs-impl-type>User</cs-impl-type>
</hint>
</hints>
You can now write your implementation of the User class as desired and compile it with the generated code.
Replacing the default implementation with a custom implementation
Running the compiler with the following hint will change the name of the C# type from User to UserData.
<?xml version=”1.0” ?>
<hints>
<hint>
<java-class>User</java-class>
<cs-impl-type>UserData</cs-impl-type>
</hint>
</hints>
Using the above hint, the compiler no longer generates the User class or the UserData class. However, all of the other classes are generated with the assumption that you will implement the UserData class.
Notice that after generating code using the example hints file, the ValueFactory no longer refers to the User class. Rather, it refers to the UserData class.
public virtual System.Type GetValueType() {
return typeof(UserData);
}
public virtual UserData CreateObject() {
return new UserData();
}
public virtual void InitObject(UserValueData src_data,
UserData dst_object) {
dst_object.Name = src_data.Name;
dst_object.Password = src_data.Password;
}
public virtual void InitData(UserData src_object,
UserValueData dst_data) {
dst_data.Name = src_object.Name;
dst_data.Password = src_object.Password;
}
You could now write a UserData class (as shown in the following example) and use it with the generated code.
[System.Serializable] public class UserData {
private string _Name;
private string _Password;
public UserData() {
}
public UserData(string name, string password) {
_Name = name;
_Password = password;
}
internal void Init(string name, string password) {
_Name = name;
_Password = password;
}
public string Name {
get {
return _Name;
}
}
public string Password {
get{
return _Password;
}
}
}
You cannot use this class as is. In order for this class to compile, you will need to expose visible properties (or fields) to InitObject and InitData called Name and Password (See the code for InitObject and InitData in the generated ValueFactory class).
To fix this you can either add visible properties or change the field names to be Name and Password and make them visible to the generated code.
While this is straightforward, you may not want to expose the class fields. Rather you might want to keep your class as shown above. This means you need to take over the work of InitObject and InitData and rewrite the hints file.
<?xml version=”1.0” ?>
<hints>
<hint>
<java-class>User</java-class>
<cs-impl-type>UserData</cs-impl-type>
<mode>custom</mode>
</hint>
</hints>
The only difference between this hint file and the previous one is that the mode is set to custom. The generated code changes very little. In fact the only difference is in the InitObject and InitData methods. They are generated as follows:
public abstract class UserValueFactory : CORBA.ValueFactory {
public abstract void InitObject(UserValueData src_data,
UserData dst_object);
public abstract void InitData(UserData src_object,
UserValueData dst_data);
}
Notice that the class and these methods are no longer concrete. You will need to provide a factory for the User type now, but it is a trivial implementation:
public class UserFactory: UserValueFactory {
public override void InitObject(UserValueData src_data,
UserData dst_object) {
dst_object.Init(src_data.Name, src_data.Password);
}
public override void InitData(UserData src_object,
UserValueData dst_data) {
dst_data.Name = src_object.Name;
dst_data.Password = src_object.Password;
}
}
This ValueFactory will automatically be registered as the ValueFactory for the User Java class as long as one of the Helper classes in the generated code is used. To explicitly register a ValueFactory you can either call ORB.RegisterValueFactory(), or you can call ORB.RegisterAssembly() which will register all of the factories in the provided assembly.
Mapping interfaces with methods
Consider the Java interface:
public interface Principal {
public String getUserName();
}
and the Java class:
public class User implements Principal, java.io.Serializable {
private String name;
private String password;
public User(String name, String password) {
this.name = name;
this.password = password;
}
public String getUserName() {
return name;
}
}
Running the compiler on this interface and class, without hints for both the interface and the class, will result in the following warning:
java2cs: (warning)The type Principal requires a mapping hint to be fully valid (e.g., method signatures will be ignored).
java2cs: (warning)The type User requires a mapping hint to be fully valid (e.g., method signatures will be ignored).
This warning indicates that the interface (which is not a remote interface) has methods that are ignored by the java2cs compiler. The compiler ignores these methods as it is not possible for the compiler to map methods that are not designed to be invoked remotely. This is due to the fact that the parameters that such methods take may be valid only in the local contexts. If you look at the generated code, the compiler will generate the following code for Principal:
public interface Principal {
}
and the following code for User:
[System.Serializable] public class User : Principal {
...
}
The compiler ignored the generating the code for the getUserName method. The compiler warnings suggest that this is most likely not what is expected, and therefore you must use a hint to map this to an appropriate .NET interface.
Let's say that we use the following hint file (note that we are not providing a hint for User):
<hints>
<hint>
<java-class>Principal</java-class>
<cs-sig-type>IPrincipal</cs-sig-type>
</hint>
</hints>
This maps the interface Principle to the C# interface IPrinciple (which the compiler will not generate). Let us say we also add the IAuthenticatable to our .NET code as follows (note that you could use an existing interface, such as System.Security.Principals.IPrincipal):
public interface IPrincipal {
string GetName();
}
Now, this works better. The generated User extends IPrincipal:
[System.Serializable] public class User : IPrincipal {
...
}
The compiler would have still generated the warning:
java2cs: (warning)The type User requires a mapping hint to be fully valid (e.g., method signatures will be ignored).
Now it is obvious why this warning is generated. The User class that is generated cannot possibly know that the IPrincipal has a method called GetName that needs to be implemented. And even if it did, it could not possibly know how the method was implemented.
The rule here, therefore, is that whenever the compiler generates a value class, which it knows contains methods that need to be implemented, it will generate the warning.
Using signature type to hide implementation details
In the above case the User type implemented an interface. There are many cases where we develop classes that implement interfaces but our classes are private implementations that are never exposed to the user. For example, consider an Iterator of any collection. While the Iterator interface is public, all implementations of it are typically hidden and are never exposed to the user.
For example, if User were one such type, you do not want your ValueFactories actually exposing the type in its signatures because ValueFactories are public classes. To avoid this you can use the signature type in the hint to control what is exposed by the ValueFactory.
The following hint:
<hints>
<hint>
<java-class>Principal</java-class>
<cs-sig-type>IPrincipal</cs-sig-type>
</hint>
<hint>
<java-class>User</java-class>
<cs-sig-type>IPrincipal</cs-sig-type>
<cs-impl-type>UserData</cs-impl-type>
<mode>custom</mode>
</hint>
</hints>
generates the following ValueFactory:
public abstract class UserValueFactory : CORBA.ValueFactory {
public virtual System.Type GetValueType() {
return typeof(UserData);
}
public virtual IPrincipal CreateObject() {
return new UserData();
}
public abstract void InitObject(UserValueData src_data, IPrincipal
dst_object);
public abstract void InitData(IPrincipal src_object, UserValueData
dst_data);
}
Note that while the implementation that the factory uses is UserData, all of the signatures use IPrincipal.
Explicit factory code
Sometimes it is just convenient to write all the factory code yourself. To do this, use the following hints:
<hints>
<hint>
<java-class>Principal</java-class>
<cs-sig-type>IPrincipal</cs-sig-type>
</hint>
<hint>
<java-class>User</java-class>
<cs-sig-type>UserData</cs-sig-type>
<mode>custom</mode>
</hint>
</hints>
The only changes from the previously generated code are the GetValueType and CreateObject methods which are also abstract now.
public abstract System.Type GetValueType();
public abstract UserData CreateObject();
The key here is that cs-sig-type element is used in the hint rather than cs-impl-type. This instructs the compiler to exclude all references to the implementation class.
Notice that you can still tweak the other aspects of the hints to change other code generation aspects. For example the following hint:
<hints>
<hint>
<java-class>Principal</java-class>
<cs-sig-type>IPrincipal</cs-sig-type>
</hint>
<hint>
<java-class>User</java-class>
<cs-sig-type>UserData</cs-sig-type>
</hint>
</hints>
still results in the InitObject and InitData methods being generated as shown below:
public virtual void InitObject(UserValueData src_data,
UserData dst_object) {
dst_object.Name = src_data.Name;
dst_object.Password = src_data.Password;
}
public virtual void InitData(UserData src_object,
UserValueData dst_data) {
dst_data.Name = src_object.Name;
dst_data.Password = src_object.Password;
}
Immutables
Consider the earlier example of the UserData class with one slight modification. In the following example we removed the init method and the default void constructor:
[System.Serializable] public class UserData {
private string _Name;
private string _Password;
public UserData(String name, string password) {
_Name = name;
_Password = password;
}
public string Name {
get {
return _Name;
}
}
public string Password {
get{
return _Password;
}
}
}
This is an example of a class that cannot be created without initializing its fields. Also notice that once created there is no way to initialize its fields. There are no methods to set the Name and Password fields, but here we are reading the state of the object from the network and we need to set the object's state to the exact values we read.
However, our ValueFactory creates the object in the CreateObject method and initializes it in another step (InitObject). This obviously will not work for us. To support this case, we provide the immutable mode in the hint.
Using this hint:
<hints>
<hint>
<java-class>Principal</java-class>
<cs-sig-type>IPrincipal</cs-sig-type>
</hint>
<hint>
<java-class>User</java-class>
<cs-sig-type>IPrincipal</cs-sig-type>
<cs-impl-type>UserData</cs-impl-type>
<mode>immutable</mode>
</hint>
</hints>
results in the following signature for InitObject:
public abstract IPrincipal InitObject(UserValueData src_data);
Also, the CreateObject call is no longer generated (abstract or otherwise).
Notice here how the InitObject returns an IPrincipal rather than receiving one as argument. This allows you to write a ValueFactory that creates a UserData with the value data that has already been unmarshaled and return it.
Such a ValueFactory might look like this:
public class UserFactory: UserValueFactory {
public override IPrincipal InitObject(UserValueData src_data);
return new UserData(src_data.Name, src_data.Password);
}
public override void InitData(UserData src_object, UserValueData dst_data)
{
dst_data.Name = src_object.Name;
dst_data.Password = src_object.Password;
}
}
Be aware that with the immutable mode you are responsible for using all the state in the data object (which will include all the data for all of the base classes as well) to initialize your immutable object as appropriate.
Custom marshaling
When writing passwords to the network you may want to encrypt them to prevent passwords from being sent in the clear. To do this you should have the Java class use custom marshaling.
Consider the following version of the Java User class:
public class User implements Principal, java.io.Serializable {
private String name;
transient private String password;
public User(String name, String password) {
this.name = name;
this.password = password;
}
public String getUserName() {
return name;
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
s.defaultWriteObject();
s.writeObject(encrypt(password));
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
password = encrypt((String) s.readObject());
}
private String encrypt(String val) {
char[] result = new char[val.length()];
for (int 1 = 0; i < val.length(); i++) {
result[i] = (char) (((byte) val.charAt(i)) ^ 0x77);
}
return new String(result);
}
}
This is a custom marshaled Java Serializable class. The default code generation for this class (with no hints) shows some changes. The value class is no longer generated. This is because the compiler knows that your class is custom marshaled, so it cannot possibly generate the appropriate fields in your class. However, it does know to generate the ValueData class, as that represents the fields (the non-transient fields) that would be marshaled if the class used default marshaling. As show in the code sample above, the class also marshals some additional data.
The ValueData is generated as follows:
public class UserValueData {
public string Name;
}
The ValueFactory is generated as follows:
public abstract System.Type GetValueType();
public abstract User CreateObject();
public abstract void ReadObject(UserValueData data,
CORBA.ObjectInputStream input,
User obj);
public abstract void WriteObject(User obj,
UserValueData data,
CORBA.ObjectOutputStream output);
public static void DefaultReadValueData(UserValueData data,
CORBA.ObjectInputStream input) {
...
}
public static void WriteValueData(UserValueData data,
CORBA.ObjectOutputStream output) {
...
}
Notice that the GetValueType and CreateObject methods are now abstract. The compiler requires you to provide the implementation for these as it does not know the name of your C# class. Second, note that you no longer have the InitObject and InitData methods. Instead, you have two new methods: ReadObject and WriteObject. You will have to implement these methods to provide the appropriate custom marshaling logic. As you can see, the ValueData object and the value class are still passed to the method, but in addition a Stream is also passed. This allows the custom marshaling logic to be written. And finally some additional methods (DefaultReadValueData and WriteValueData) are generated to allow the user to read or write default marshaled data.
In Java, a common use of custom marshaling is to lazy-compute serializable fields at the time of marshaling and to lazy-initialize transient fields at the time of unmarshaling. The actual marshaling remains identical. Sometimes, the custom marshaling reads and writes the default fields but adds some additional data at the end of the stream.
A sample value factory for the above Java class is shown below, using this implementation of UserData.
[System.Serializable] public class UserData {
private string _Name;
private string _Password;
public UserData() {
}
public UserData(string name, string password) {
_Name = name;
_Password = password;
}
internal Init(string name, string password) {
_Name = name;
_Password = password;
}
public string Name {
get {
return _Name;
}
}
public string Password {
get{
return _Password;
}
}
}
The ValueFactory:
public class UserFactory : UserValueFactory {
public override System.Type GetValueType() {
return typeof(UserData);
}
public override UserData CreateObject() {
return new UserData();
}
public string Encrypt(string val) {
char[] ersult = new char[val.Length];
for(int i = 0; i < val.Length; i++) {
result[i] = (char) (((byte) val[i] ^ 0x77);
}
return new string(result);
}
public override void ReadObject(UserValueData data,
CORBA.ObjectInputStream input,
User obj) {
DefaultReadValueData(data, input);
obj.Init(data.Name, Encrypt(input.ReadString()));
}
public override void WriteObject(User obj,
UserValueData data,
CORBA.ObjectOutputStream output) {
data.Name = obj.Name;
DefaultWriteValueData(data, output);
output.WriteObject(Encrypt(obj.Password));
}
}
As shown earlier, you may modify the name of the value object and change the signature that is exposed using the other hint techniques. You may also write additional data after the DefaultWriteValueData and read the same addition after the DefaultReadValueData. In addition, calling DefaultWrite/ReadValueData is not required.
Hints file schema
The hints file schema is as follows:
<?xml version="1.0" ?>
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.w3.org/2001/XMLSchema">
<xsd:element name="hints">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="hint" type="hintType" minOccurs="1"
maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="hintType">
<xsd:sequence>
<xsd:element name="java-class" type="xsd:string"/>
<xsd:element name="cs-sig-type" type="xsd:string" minOccurs="0"/>
<xsd:element name="cs-impl-type" type="xsd:string" minOccurs="0"/>
<xsd:element name="mode" type="modeType" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:simpleType name="modeType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="automatic"/>
<xsd:enumeration value="custom"/>
<xsd:enumeration value="immutable"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
One-to-many marshaling precedence
VisiBroker for .NET has a set of built-in value factories, that have a predetermined precedence. When there is an ambiguity about how to marshal a particular type, the default behavior is as follows:
// we need a deterministic ordering for value factories, so that the user
// knows how types are marshaled by default. The marshaling preference is
// based on registration order, with highest priority going to the last
// factory registered...
CORBA.ValueFactory[] factories = {
// Lowest priority goes to JDK 1.4 types, since these
// are meaningless to older JDKs...
new J2EE.Factories.LinkedHashMapValueFactory(),
new J2EE.Factories.LinkedHashSetValueFactory(),
// Next in priority are the JDK 1.0 and 1.1 types,
// which are no longer in fashion...
new J2EE.Factories.HashtableValueFactory(),
new J2EE.Factories.PropertiesValueFactory(),
new J2EE.Factories.StackValueFactory(),
new J2EE.Factories.VectorValueFactory(),
// Next, we have the JDK 1.2 types (note that there
// are no relevant JDK 1.3 types)...
// First we have the "less popular" types...
new J2EE.Factories.LinkedListValueFactory(),
new J2EE.Factories.TreeMapValueFactory(),
new J2EE.Factories.TreeSetValueFactory(),
// Then we have the "most popular" types...
new J2EE.Factories.HashMapValueFactory(),
new J2EE.Factories.HashSetValueFactory(),
// And finally ArrayList wins the overall popularity contest!
new J2EE.Factories.ArrayListValueFactory(),
};
foreach(CORBA.ValueFactory factory in factories) {
orb.RegisterValueFactory(factory);
}
 
Items lower in the array take precedence over items higher in the array. Of course, that may not be what you require. In cases where you require a different precedence, you need to manually override the default behavior. The simplest way to do this is to register your preferred ValueFactories explicitly in your main program. If you want java.util.Hashtable to take precedence over competing types (such as java.util.HashMap), then your main program would contain:
CORBA.ORB orb = CORBA.ORB.Init();
orb.RegisterValueFactory(J2EE.Util.HashtableValueFactory.GetSingleton());
The ORB.Init is setting up all the default ORB behavior, including doing the ValueFactory registration shown above. This default has the HashMap ValueFactory taking precedence over the Hashtable ValueFactory. But then after initializing the ORB, we explicitly register the Hashtable ValueFactory, which will cause this to take precedence over all the previous ValueFactory registrations.