Slice interfaces map to proxies on the client side. A proxy is simply a Java interface with operations that correspond to the operations defined in the Slice interface.
The compiler generates quite few source files for each Slice interface. In general, for an interface
<interface‑name>, the following source files are created by the compiler:
•
_<interface‑name>Operations.java
_<interface‑name>OperationsNC.java
These are the files that contain code that is relevant to the client side. The compiler also generates a file that is specific to the server side, plus three additional files:
•
_<interface‑name>Disp.java
•
_<interface‑name>Del.java
•
_<interface‑name>DelD.java
•
_<interface‑name>DelM.java
On the client side, Slice interfaces map to Java interfaces with member functions that correspond to the operations on those interfaces. Consider the following simple interface:
public interface SimplePrx extends Ice.ObjectPrx {
public void op();
public void op(java.util.Map<String, String> __context);
}
As you can see, the compiler generates a proxy interface SimplePrx. In general, the generated name is
<interface‑name>Prx. If an interface is nested in a module
M, the generated class is part of package
M, so the fully-qualified name is
M.<interface‑name>Prx.
In the client’s address space, an instance of SimplePrx is the local ambassador for a remote instance of the
Simple interface in a server and is known as a
proxy instance. All the details about the server-side object, such as its address, what protocol to use, and its object identity are encapsulated in that instance.
Note that SimplePrx inherits from
Ice.ObjectPrx. This reflects the fact that all Ice interfaces implicitly inherit from
Ice::Object.
For each operation in the interface, the proxy class has a member function of the same name. For the preceding example, we find that the operation
op has been mapped to the member function
op. Also note that
op is overloaded: the second version of
op has a parameter
__context of type
java.util.Map<String, String>. This parameter is for use by the Ice run time to store information about how to deliver a request. You normally do not need to use it. (We examine the
__context parameter in detail in
Chapter 28. The parameter is also used by IceStorm—see
Chapter 41.)
Because all the <interface‑name>Prx types are interfaces, you cannot instantiate an object of such a type. Instead, proxy instances are always instantiated on behalf of the client by the Ice run time, so client code never has any need to instantiate a proxy directly.The proxy references handed out by the Ice run time are always of type
<interface‑name>Prx; the concrete implementation of the interface is part of the Ice run time and does not concern application code.
A value of null denotes the null proxy. The null proxy is a dedicated value that indicates that a proxy points "nowhere" (denotes no object).
10.11.2 The Ice.ObjectPrx Interface
All Ice objects have Object as the ultimate ancestor type, so all proxies inherit from
Ice.ObjectPrx.
ObjectPrx provides a number of methods:
package Ice;
public interface ObjectPrx {
boolean equals(java.lang.Object r);
Identity ice_getIdentity();
int ice_hash();
boolean ice_isA(String __id);
String ice_id();
void ice_ping();
// ...
}
This operation compares two proxies for equality. Note that all aspects of proxies are compared by this operation, such as the communication endpoints for the proxy. This means that, in general, if two proxies compare unequal, that does
not imply that they denote different objects. For example, if two proxies denote the same Ice object via different transport endpoints,
equals returns
false even though the proxies denote the same object.
module Ice {
struct Identity {
string name;
string category;
};
};
Ice.ObjectPrx o1 = ...;
Ice.ObjectPrx o2 = ...;
Ice.Identity i1 = o1.ice_getIdentity();
Ice.Identity i2 = o2.ice_getIdentity();
if (i1.equals(i2))
// o1 and o2 denote the same object
else
// o1 and o2 denote different objects
Ice.ObjectPrx o = ...;
if (o != null && o.ice_isA("::Printer"))
// o denotes a Printer object
else
// o denotes some other type of object
Note that there are other methods in ObjectPrx, not shown here. These methods provide different ways to dispatch a call. (We discuss these methods in
Chapter 28.)
For each Slice interface, apart from the proxy interface, the Slice-to-Java compiler creates a helper class: for an interface
Simple, the name of the generated helper class is
SimplePrxHelper. The helper classes contains two methods of to support down-casting:
public final class SimplePrxHelper
extends Ice.ObjectPrxHelper implements SimplePrx {
public static SimplePrx checkedCast(Ice.ObjectPrx b) {
// ...
}
public static SimplePrx checkedCast(Ice.ObjectPrx b,
Ice.Context ctx) {
// ...
}
public static SimplePrx uncheckedCast(Ice.ObjectPrx b) {
// ...
}
// ...
}
Both the checkedCast and
uncheckedCast methods implement a down-cast: if the passed proxy is a proxy for an object of type
Simple, or a proxy for an object with a type derived from
Simple, the cast returns a non-null reference to a proxy of type
SimplePrx; otherwise, if the passed proxy denotes an object of a different type (or if the passed proxy is null), the cast returns a null reference.
Given a proxy of any type, you can use a checkedCast to determine whether the corresponding object supports a given type, for example:
Ice.ObjectPrx obj = ...; // Get a proxy from somewhere...
SimplePrx simple = SimplePrxHelper.checkedCast(obj);
if (simple != null)
// Object supports the Simple interface...
else
// Object is not of type Simple...
Note that a checkedCast contacts the server. This is necessary because only the implementation of a proxy in the server has definite knowledge of the type of an object. As a result, a
checkedCast may throw a
ConnectTimeoutException or an
ObjectNotExistException. (This also explains the need for the helper class: the Ice run time must contact the server, so we cannot use a Java down-cast.)
In contrast, an uncheckedCast does not contact the server and unconditionally returns a proxy of the requested type. However, if you do use an
uncheckedCast, you must be certain that the proxy really does support the type you are casting to; otherwise, if you get it wrong, you will most likely get a run-time exception when you invoke an operation on the proxy. The most likely error for such a type mismatch is
OperationNotExistException. However, other exceptions, such as a marshaling exception are possible as well. And, if the object happens to have an operation with the correct name, but different parameter types, no exception may be reported at all and you simply end up sending the invocation to an object of the wrong type; that object may do rather non-sensical things. To illustrate this, consider the following two interfaces:
interface Process {
void launch(int stackSize, int dataSize);
};
// ...
interface Rocket {
void launch(float xCoord, float yCoord);
};
Suppose you expect to receive a proxy for a Process object and use an
uncheckedCast to down-cast the proxy:
Ice.ObjectPrx obj = ...; // Get proxy...
ProcessPrx process
= ProcessPrxHelper.uncheckedCast(obj); // No worries...
process.launch(40, 60); // Oops...
If the proxy you received actually denotes a Rocket object, the error will go undetected by the Ice run time: because
int and
float have the same size and because the Ice protocol does not tag data with its type on the wire, the implementation of
Rocket::launch will simply misinterpret the passed integers as floating-point numbers.
In fairness, this example is somewhat contrived. For such a mistake to go unnoticed at run time, both objects must have an operation with the same name and, in addition, the run-time arguments passed to the operation must have a total marshaled size that matches the number of bytes that are expected by the unmarshaling code on the server side. In practice, this is extremely rare and an incorrect
uncheckedCast typically results in a run-time exception.
A final warning about down-casts: you must use either a checkedCast or an
uncheckedCast to down-cast a proxy. If you use a Java cast, the behavior is undefined.
The base proxy class ObjectPrx supports a variety of methods for customizing a proxy (see
Section 28.10). Since proxies are immutable, each of these "factory methods" returns a copy of the original proxy that contains the desired modification. For example, you can obtain a proxy configured with a ten second timeout as shown below:
A factory method returns a new proxy object if the requested modification differs from the current proxy, otherwise it returns the current proxy. With few exceptions, factory methods return a proxy of the same type as the current proxy, therefore it is generally not necessary to repeat a
checkedCast or
uncheckedCast after using a factory method. However, a regular cast is still required, as shown in the example below:
The only exceptions are the factory methods ice_facet and
ice_identity. Calls to either of these methods may produce a proxy for an object of an unrelated type, therefore they return a base proxy that you must subsequently down-cast to an appropriate type.
Proxies provide an equals method that compares proxies:
interface ObjectPrx {
boolean equals(java.lang.Object r);
}
Note that proxy comparison with equals uses
all of the information in a proxy for the comparison. This means that not only the object identity must match for a comparison to succeed, but other details inside the proxy, such as the protocol and endpoint information, must be the same. In other words, comparison with
equals tests for
proxy identity,
not object identity. A common mistake is to write code along the following lines:
Ice.ObjectPrx p1 = ...; // Get a proxy...
Ice.ObjectPrx p2 = ...; // Get another proxy...
if (p1.equals(p2)) {
// p1 and p2 denote different objects // WRONG!
} else {
// p1 and p2 denote the same object // Correct
}
Even though p1 and
p2 differ, they may denote the same Ice object. This can happen because, for example, both
p1 and
p2 embed the same object identity, but each use a different protocol to contact the target object. Similarly, the protocols may be the same, but denote different endpoints (because a single Ice object can be contacted via several different transport endpoints). In other words, if two proxies compare equal with
equals, we know that the two proxies denote the same object (because they are identical in all respects); however, if two proxies compare unequal with
equals, we know absolutely nothing: the proxies may or may not denote the same object.
package Ice;
public final class Util {
public static int proxyIdentityCompare(ObjectPrx lhs,
ObjectPrx rhs);
public static int proxyIdentityAndFacetCompare(ObjectPrx lhs,
ObjectPrx rhs);
// ...
}
proxyIdentityCompare allows you to correctly compare proxies for identity:
Ice.ObjectPrx p1 = ...; // Get a proxy...
Ice.ObjectPrx p2 = ...; // Get another proxy...
if (Ice.Util.proxyIdentityCompare(p1, p2) != 0) {
// p1 and p2 denote different objects // Correct
} else {
// p1 and p2 denote the same object // Correct
}
The function returns 0 if the identities are equal, −1 if
p1 is less than
p2, and 1 if
p1 is greater than
p2. (The comparison uses
name as the major and
category as the minor sort key.)
The proxyIdentityAndFacetCompare function behaves similarly, but compares both the identity and the facet name (see
Chapter 30).
package Ice;
public class ProxyIdentityKey {
public ProxyIdentityKey(Ice.ObjectPrx proxy);
public int hashCode();
public boolean equals(java.lang.Object obj);
public Ice.ObjectPrx getProxy();
}
public class ProxyIdentityFacetKey {
public ProxyIdentityFacetKey(Ice.ObjectPrx proxy);
public int hashCode();
public boolean equals(java.lang.Object obj);
public Ice.ObjectPrx getProxy();
}
The constructor caches the identity and the hash code of the passed proxy, so calls to
hashCode and
equals can be evaluated efficiently. The
getProxy method returns the proxy that was passed to the constructor.
As for the comparison functions, ProxyIdentityKey only uses the proxy’s identity, whereas
ProxyIdentityFacetKey also includes the facet name.