Frequently Asked Questions
CORBA provides a reverse mapping from Java to IDL. Of course, this begs the question of why Ice does not provide such a mapping as well.
Up-front, it must be understood that there are a number serious problems with the CORBA approach to this reverse mapping. For example:
// Java
public class StringExample
{
public void putString(String val) throws ParseException;
}
Even for this trivial example, there are problems:
- How should we map the ParseException that can be thrown by the method? At the very least, we would have to generate a Slice exception that, as faithfully as possible, replicates the semantics of java.text.ParseException. Because methods can throw unchecked exceptions as well, the mapping would have to include mappings for all of the exceptions that derive from java.lang.RuntimeException.
- The caller of putString can pass any string, including null. However, Slice does not have the concept of a null string because some implementation languages do not support this concept. To faithfully replicate the semantics of the Java method, we would have to map the input string to a sequence that contains either zero or one elements, depending on whether the string is a null string or not, or we would have to map the input string to a Slice class (which can be null) that contains the string. Because most parameters in Java can be null (except for value in-parameters), this means that almost everything ends up being mapped as a sequence or class, quickly turning the generated API into an unusable mess.
Now consider the following Java definitions:
// Java
public class IteratorExample
{
public java.util.ListIterator getIterator();
}
In a real Java program, the iterator would iterate over some collection of values. However, it is not at all obvious how the same functionality should be represented in Slice. Here is one attempt:
// Slice
interface IteratorExample
{
java::util::ListIterator* getIterator();
};
At first glance, this looks harmless enough: the Slice operation returns a proxy to a corresponding Slice interface that, presumably, mirrors the behavior of the Java iterator. An immediate question is what the name of this Slice interface should be. Should it really be java::util::ListIterator? As it turns out, we cannot use that name for the Slice interface because, after translating back to Java, the Java class name that is generated by the Slice-to-Java mapping would be java.util.ListIterator but that type is already defined by the JDK. So, we would have to give the Slice interface a different name, such as Ice::JavaMapping::java::util::ListIterator. This avoids the name clash, but creates another problem: after translating back to Java, the generated type is not the same as java.util.ListIterator, meaning that it cannot be passed where another method expects a real Java iterator. In other words, the translation process would result in a type that emulates the behavior of a Java predefined type but is not the same as the predefined type. This makes interoperability of existing Java code that expects to deal with JDK predefined types very difficult.
Another issue is that a type such as java.util.ListIterator makes perfect sense to a Java client. However, it is awkward to use with other languages, such as C# or C++. For example, in C#, the natural way to iterate over a collection is to use a foreach loop whereas, in C++, programmers would use an STL iterator. Even if Ice were to provide C# and C++ implementations of the ListIterator type (and hundreds of other types in the JDK), few if any of these types would have APIs that are natural to use in C# or C++ (let alone faithfully preserve the semantics of the corresponding JDK types).
Of course, there is also the question of efficiency. Obviously, when the user of our iterator calls next, the iterator should return the next element of the collection. Seeing that the point of a Java-to-Slice mapping would be to enable remote access to existing Java objects, the iterator would make a remote call for each call to next, hasNext, previous, and so on. Even if this would work, the performance penalty would be prohibitive: the original Java class was written with the knowledge that the caller is in the same address space as the implementation of the class, so fine-grained methods such as hasNext are perfectly fine. However, the chances that the class will still perform acceptably if all of its methods become remote procedure calls are essentially zero.
Now consider the following Java definition:
// Java
public class PrivateExample
{
public void setVal(int val) { /* ... */ }
private bool checkVal(int val) { /* ... */ }
// ...
private int val;
}
It is not at all obvious what should happen with the private method and data member. For one, Slice has no concept of private operations or data members; if these were to be preserved in Slice, they would, by necessity, have to become public members. Or, alternatively, the mapping could simply ignore everything that is private and, instead, generate a Slice class that contains operations and data members only for the corresponding public Java methods and data members. But, of course, doing this then means that the developer would have to re-create the missing private implementations. For anything but the most trivial classes, doing so is labor-intensive and error-prone. And, worse, because there are very few real-life Java classes that do not have private methods or data members, the reverse mapping would fail to provide what it would be intended for, namely, to make it easy to access existing Java object implementations from remote clients (not necessarily written in Java).
There are numerous other problems with the idea of a Java-to-Slice mapping, such as threading issues, how to choose object identities for Ice proxies that correspond to Java objects, and how to choose the object adapters and their endpoints for these objects.
Another problem that is impossible to solve relates to object life cycle: in a Java program, as soon as the last reference to a Java object goes out of scope, the object becomes eligible for garbage collection and will eventually be reclaimed by the Java garbage collector. Java interfaces are created with this in mind and usually do not provide explicit destroy methods. Of course, this begs the question of how an Ice server using our hypothetical mapping would realize that a remote client has lost interest in an object and, therefore, that it now should reclaim that object. (Previous attempts at transparently extending garbage collection to the remote case turned out to be not scalable, as evidenced by DCOM.)
The foregoing is only a brief sampling of the problems associated with a Java-to-Slice mapping—it should be sufficient to convince you that such a mapping would be pragmatically useless.
Incidentally, this is exactly the experience made by the CORBA community. Apart from the semantic problems, “the IDL that results from applying the reverse mapping to Java class definitions is so abstruse that developers generally avoid it”.
If you want to interoperate remotely with existing Java applications, you have two choices:
- If the remote clients are implemented in Java as well, consider using Java’s RMI (Remote Method Invocation). This can be a cost-effective (though not necessarily well-performing) option.
- If the remote clients are implemented in languages other than Java, write Slice definitions that capture the public interactions with the existing Java objects and implement these definitions as a thin (and, as much as possible, stateless) server that forwards invocations to the existing Java implementation. This is an application of the Façade Pattern and usually a cost-effective approach to enabling remote access to legacy code. In addition, this approach ensures that the interfaces provided to clients have appropriate semantics and correctly deal with life cycle and threading issues, and it avoids creating Slice definitions that no-one in their right mind would use.