Advanced Usage

Parameters that must be converted to proxy by jInterflex

So far, our examples of method invocations had parameter types or return types that were always available at runtime (String, etc.). But what happens if some of them must be proxy ? jInterflex will do the necessary conversions between real and proxy types automatically, even with vararg parameters. We will see how to specify the information to jInterflex in order to make it possible.

Case 1: We already have the auxiliary interface

If we have already created the auxiliary interface for this type, we can simply pass the parameter to the method (or use the return type) using this proxy type. Let’s see an example.

If we want to use this method of the Logger class:

public void setParent(Logger parent);

the Logger object may not be available at runtime, so we can’t write it as is in our auxiliary interface. We have an auxiliary interface already created for it (it is the PLogger type itself), so we can add the method to our interface, replacing Logger with PLogger:

@JInterflexProxy(realClassName="java.util.logging.Logger")
public interface PLogger {

    public static final StaticPart STATIC =  JInterflex.proxy(StaticPart.class);

    public interface StaticPart {

        public PLogger getLogger(String name);
    }

    public void setParent(PLogger parent);
}

and simply pass the argument when it has to be called:

    PLogger log =  PLogger.STATIC.getLogger("myLogger");
    PLogger parentLog =  PLogger.STATIC.getLogger("parentLogger");
    log.setParent(parentLog);

Case 2: We don’t have the auxiliary interface yet

If we don’t have the auxiliary interface, we can create it. If this type is only used to hold the object, but we don’t need to build instances of it, invoke its static methods or read its public fields, we can write an auxiliary interface without the inner class (the "static" or "class" part of it). Let’s see it in action with an example.

We will use this method of the Logger class:

public Filter getFilter();

We must create an empty interface for the Filter class and add the method to our interface replacing Filter with PFilter:

@JInterflexProxy(realClassName="java.util.logging.Filter")
public interface PFilter {}

@JInterflexProxy(realClassName="java.util.logging.Logger")
public interface PLogger {

    public static final StaticPart STATIC =  JInterflex.proxy(StaticPart.class);

    public interface StaticPart {

        public PLogger getLogger(String name);
    }

    public PFilter getFilter();
}

Now we can execute this code:

PLogger log =  PLogger.STATIC.getLogger("myLogger");
PFilter filter = log.getFilter();
// -- We can pass the filter object to whatever method that needs it...

Proxy parameters that inherit or implement other proxy parameters

Sometimes we need to use methods that have parameters not always available at runtime, but which are interfaces or abstract classes. Let’s put an example.

If we want to use this method of the class Logger:

public void addHandler(Handler handler);

the Handler type is an interface that may not be available at runtime, so we can create an empty auxiliary interface for it, but when we pass the handler parameter, it will be a concrete type which is an implementation of Handler. What must we do in these situations ? We must use the inheritance of interfaces.

We will create auxiliary interfaces for all the types to use:

@JInterflexProxy(realClassName="java.util.logging.Logger")
public interface PLogger {

    public static final StaticPart STATIC =  JInterflex.proxy(StaticPart.class);

    public interface StaticPart {

        public PLogger getLogger(String name);
    }

    public void addHandler(PHandler handler);
}

@JInterflexProxy(realClassName="java.util.logging.Handler")
public interface PHandler {}

@JInterflexProxy(realClassName="java.util.logging.ConsoleHandler")
public interface PConsoleHandler extends PHandler {

    public static final StaticPart STATIC = JInterflex.proxy(StaticPart.class);

    public interface StaticPart {

        public PConsoleHandler ctor();
    }
}

We have created an empty auxiliary interface for Handler (PHandler) and an interface for the concrete type ConsoleHandler (PConsoleHandler). This last interface will have also a static part (the inner interface), because we want to create ConsoleHandler objects and for this we will need the constructor.

Now we can write this code:

PLogger log =  PLogger.STATIC.getLogger("myLogger");
log.addHandler(PConsoleHandler.STATIC.ctor());

In the signature of the method addHandler of our auxiliary interface we have used the interface PHandler, but the parameter that we pass to the method is a PConsoleHandler type. In order to do this, we have defined the PConsoleHandler interface to extend from the interface PHandler. We can’t define the interface method with the parameter PConsoleHandler directly, because jInterflex must know the signature of the method to invoke, including the types of the parameters exactly as they are declared in the real class to access.

Collections or arrays of classes that could not be available at runtime

If we must work with methods that handle collections or arrays of types that could not be available at runtime, there must be a way to pass their parameters or use their return types. For these cases jInterflex doesn’t do an automatic conversion and there are two possible strategies:

  • Adapters (the recommended way)

  • Converters (the last resort)

The adapters are containers that handle proxy objects externally, but that internally store containers of real objects. This approach offers an easy way of handling the elements, while ensuring a good performance.

The converters allow to convert a proxy object to a real object and viceversa. These objects allow to handle manually the objects before and after inserting or extracting an element in a collection or array. In other words, this is a "low-level" handling of proxy objects and is not recommended unless there is no alternative (like using a special container with objects possibly not available in runtime for which there is no adapter available). To get a converter of a proxy object the static method converterOf of the class JInterflexUtils must be used, passing as a parameter the auxiliary interface associated to the proxy class to handle. As an example, we could write this to convert to and from a proxy PConsoleHandler:

IConverter<PConsoleHandler> converter = JInterflexUtils.converterOf(PConsoleHandler.class);
PConsoleHandler proxyHandler = PConsoleHandler.STATIC.ctor();
Object realHandler = converter.toReal(proxyHandler);
PConsoleHandler sameProxyHandler = converter.toProxy(realHandler);

We will see in detail some examples of use with collections and with arrays. For these examples we’ll use adapters, which is the recommended way.

Note
Of course, using collections or arrays of types that are always available at runtime don’t require any special handling, just declare the methods in the auxiliary interface and they will work out of the box (this case would belong to the "basic usage" of jInterflex).

Collections

It’s better to see an example for an easier understanding. Suppose we want to execute this code but we may not have at runtime neither the Logger object nor the the class Collections (of course this is only an example with illustrative purposes, both classes are always available at runtime):

Logger logger1 = Logger.getLogger("logger1");
Logger logger2 = Logger.getLogger("logger2");
List<Logger> origLoggers = new ArrayList<Logger>();
origLoggers.add(logger1);
origLoggers.add(logger2);
List<Logger> syncLoggers = Collections.synchronizedList(origLoggers);
Logger logger = syncLoggers.get(0);
// -- Use the logger object...

We will first need to include the auxiliary interface for the Collections class, which we’ll name PCollections, including the method that we need:

@JInterflexProxy(realClassName="java.util.Collections")
public interface PCollections {

    public static final StaticPart STATIC =  JInterflex.proxy(StaticPart.class);

    public interface StaticPart {

        public <T> List<T> synchronizedList(List<T> list);
    }
}

For the method we have used the same signature as in the Collections class (omitting the static keyword), including the generic type T, because this way we can copy the signature right from the javadocs and the generic type will give us more type safety. Anyway in jInterflex the arrays and collections of objects not available at runtime will always be passed with a type Object, so we could have used the non-generic signature as well (<List<Object>> instead of <List<T>>).

For the parameter we need to pass a list of real objects (Logger), because jInterflex does not convert a list of proxies (PLogger). But for filling the list with values it’s necessary to handle proxy objects, so we will need an adapter. The simplest way to create the list of real objects is using the fluent interface of the Adapters class, which allows to create a collection of real objects adding their proxy elements:

PLogger logger1 = PLogger.STATIC.getLogger("logger1");
PLogger logger2 = PLogger.STATIC.getLogger("logger2");
List<Object> origLoggers = Adapters.ofList(new ArrayList<Object>(), PLogger.class)
                                   .adding(logger1)
                                   .adding(logger2)
                                   .toReal();

We must handle the returned list of real objects using proxy objects, so we will also need an adapter. It’s very easy to have one using the Adapters class:

List<Object> syncLoggersReal = PCollections.STATIC.synchronizedList(origLoggers);
List<PLogger> syncLoggers = Adapters.ofList(syncLoggersReal, PLogger.class);

We have given as parameters to the method ofList the same as before, the list of real objects to adapt and the type of the proxy. Now the only difference was that the list was not empty and that we didn’t follow the fluent interface, because this time we wanted a list of proxies, which was directly returned by the adapter (it implements the List interface).

At this point we can write the whole snippet of code equivalent to the original one:

PLogger logger1 = PLogger.STATIC.getLogger("logger1");
PLogger logger2 = PLogger.STATIC.getLogger("logger2");
List<Object> origLoggers = Adapters.ofList(new ArrayList<Object>(), PLogger.class)
                                   .adding(logger1)
                                   .adding(logger2)
                                   .toReal();
List<Object> syncLoggersReal = PCollections.STATIC.synchronizedList(origLoggers);
List<PLogger> syncLoggers = Adapters.ofList(syncLoggersReal, PLogger.class);
PLogger logger = syncLoggers.get(0);
// -- Use the logger object...

Besides the List adapter there is also an adapter for Maps and one for Collections.

Arrays

With arrays we have the same philosophy as with collections, but there is a difference: the declaration of the method parameter in the auxiliary interface must have the information of the type of objects that the array will contain (it’s not enough to specify that they are Object[]). As the types may not be available at runtime, we can’t write, for instance, Logger[], so we must specify them with an annotation. This annotation is not necessary for return types, only for method parameters.

Let’s see an example, where we want to execute the method getHandlers() of the class Logger using jInterflex (we suppose the class Logger and Handler may not be available at runtime, like in previous examples). The code to invoke with real objects would be:

Logger logger1 = Logger.getLogger("logger1");
Handler[] origHandlers = logger1.getHandlers();
// -- Use the list of handlers...

First of all, we would add the method signature of the method to the PLogger interface, but replacing Handler[] by Object[]:

@JInterflexProxy(realClassName="java.util.logging.Logger")
public interface PLogger {

    public static final StaticPart STATIC =  JInterflex.proxy(StaticPart.class);

    public interface StaticPart {

        public PLogger getLogger(String name);
    }

    // ... Other methods

    // -- New method added
    public Object[] getHandlers();
}

The return type is an array of plain Object elements analog to the use of collections. The code to call the method would be:

PLogger logger1 = PLogger.STATIC.getLogger("logger1");
Object[] realHandlers = logger1.getHandlers();
List<PHandler> proxyHandlers = Adapters.ofArray(realHandlers, PHandler.class);
// -- Use the list of handlers...

As an array adapter implements the List interface, we may declare the result of the Adapters.ofArray() as a list of proxies instead of as an ArrayAdapter, which is easier to use and not so verbose (it has less generic type parameters).

If the array of handlers appears in the parameters, we would also replace the array of the real objects by an array of Object, but this time annotating the array with an @ArrayType annotation that specifies the type of the array. The use of the adapters would be very similar to the collections example. In order to show how it works, let’s imagine that the Logger class has a setHandlers method that takes an array of Handler objects and write the code with jInterflex needed to do the method call.

We would add the method signature of the method to the PLogger interface, replacing Handler[] by Object[] and annotating it as described above:

@JInterflexProxy(realClassName="java.util.logging.Logger")
public interface PLogger {

    public static final StaticPart STATIC =  JInterflex.proxy(StaticPart.class);

    public interface StaticPart {

        public PLogger getLogger(String name);
    }

    // ... Other methods

    // -- New method added
    public void setHandlers(@ArrayType("java.util.logging.Handler") Object[] handlers);
}

The code to call the method could be:

PLogger logger1 = PLogger.STATIC.getLogger("logger1");
PHandler handler1 = ...; // -- Create the handler using jInterflex
PHandler handler2 = ...; // -- Create the handler using jInterflex
Object[] realHandlers = Adapters.ofNewArray(2, PHandler.class)
                                .setting(0, handler1)
                                .setting(1, handler2)
                                .toReal();
logger1.setHandlers(realHandlers);

We have created an adapter of a new array of two elements and we have set them using the fluent methods setting() (equivalent to List.set(), but returning the adapter, allowing to chain the calls).

Warning
The adapter of an array is a List, but a fixed size one, which can’t be expanded, so we can’t use the add() method with an ArrayAdapter. We can only set its elements with the set() or setting() methods.