Overview

Using Mandala leads to several advantages that can be divided into two subcategories:

Concurrent Programming

Whereas threads are good entities for concurrent programming in imperative languages (such as C), I believe they are not good ones (and especially Java threads) for concurrent programming in object oriented languages.

For example, in Java, Thread requires a run() method to be defined which represents the thread behavior. Invoking this method produce the same result -- from a Java newbie point a view -- as invoking start(). Is is hard to understand that a Java Thread instance is not the *real* thread but only a (bad?) representation of it. Many problems with Java Threads have already been mentioned in the literature (stop(), suspend()/resume(), scheduling and priorities which are OS dependent (no more green threads since the JDK 1.3), the thread swing model problem, etc.).

Several projects already dealt more or less with concurrency issues: But I believe a radically different approach must be chosen:
implementing concurrency using threads leads to a direct dependency between the asynchronism functionality and the actual implementation of it.
For example, consider the following server code:
        ServerSocket ss = new ServerSocket();
        while(true) {
                Socket socket = serverSocket.accept();
        	new Thread() {
                	public void run() {
                        	serve(socket);
                        }
        	}.start();
        }
    
The problem is that the asynchronism is tied to the implementation, here, one thread per call. For performance reasons, it might be better to use a thread pool. Another example, suppose you want to perform asynchronous input/output (of course, you may use the new Java IO package -- available since the JDK 1.4 -- for this purpose , but this example serves my speech): The "natural" synchronous code is:
                InputStream is = ...; OutputStream os = ...;
                byte[] b = ...; int n = ...;
                while((n = is.read(b)) != -1) {
                	os.write(b, 0, n);
                }
       
which leads when using threads "naively" to something like:
                while((n = is.read(b)) != -1) {
                        new Thread() {
                		public void run() {
                                	os.write(b, 0, n);
                                }
                	}.start();
                }

        
which is erroneous as you may have guessed: the byte buffer b is shared across multiple threads. Whereas a synchronized section solves the problem it leads to really poor performance and breaks the concurrency searched for: writers write while readers read.

Asynchronism in Mandala is based on its mandala.rami RAMI (Reflective Asynchronous Method Invocation) package which provides asynchronous method invocations using the asynchronous reference concept. Asynchronous references are the standard Java synchronous references extension to the asynchronous world: an asynchronous reference on an object allows any public method of this object to be invoked asynchronously.

Asynchronous proxy are "sugar syntactic" mirror classes of standard classes which provides transparency in asynchronous (and potentially remote, see below) method invocation. An asynchronous proxy uses an asynchronous reference for the actual asynchronous method invocation mechanism implementation.

Using asynchronous references in the server example above leads to something like:
        ServerSocket ss = new ServerSocket();

        // Creates an asynchronous proxy on a 'Service' instance
        // which contains the serve() "business" method 
        jaya.Service service = new jaya.Service(); 
        while(true) {
                Socket socket = serverSocket.accept();
        	service.rami_serve(socket);  // asynchronous call
        }
    
The advantages are the following:
  1. the asynchronous functionality is customizable (tied to the asynchronous reference) and does not appear in the code; the creator of the asynchronous proxy on the 'Service' instance has the responsibility of the asynchronous policy attached to its object. Two semantics are defined: In the example above, the default semantic is used because no semantic are defined. If a thread pool implementation is required, the instantiation must be:
                  AsynchronousSemanticFactory asf = new ThreadPooledASFactory();
                  jaya.Service = new jaya.Service(asf);
            
  2. Code simplification: no more thread in the code, everything is method invocation (either synchronous or asynchronous). I believe programmers must write their code using only method invocation which extends naturally to remote method invocation as proved by the RMI success. Similarly, concurrent programming may use asynchronous method invocations instead of dealing directly with threads.
Using asynchronous references in the asynchronous input/output example above leads to:

        InputStream is = ...; OutputStream os = ...;

        // A FIFO semantic is required! 
        AsynchronousSemanticFactory asf = new FifoASfactory();

        // Gets an asynchronous proxy on the 'os' instance
        jaya.java.io.OuputStream ramios = (jaya.java.io.OuputStream)
                        Framework.getSemiTransparentAsynchronousProxy(os, asf);

        int n = ...; FutureClient future;
        while(true) {
                byte[] b = new byte[n]; 
                n = is.read(b);
                if (n == -1) break;
                future = ramios.rami_write(b, 0, n);
        }

        doSomething(); // During the output...

        // Using a FIFO semantic guarantees that everything is written 
        // in order. Hence, when the last asynchronous method invocation
        // has terminated, we are sure everything has been done.
        future.waitUntilDone();

        And, as you see, the problem is solved by using a FIFO asynchronous
        semantic and by the creation of a new byte buffer for each
        read()/write().
    

The use of asynchronous method invocation is quite natural and prevent developers to deal with threads related problems such as scheduling, priorities and canceling (stopping(), interrupting()). For example, canceling an asynchronous operation is not performed same by a thread per call implementation (where you just call interrupt() on the target thread) than by a thread pool implementation call scheme (interrupt() is erroneous!). In Mandala, canceling an asynchronous method invocation is just a matter of invoking the method cancel() on the Future object returned. The actual implementation does all the magic for you. All this asynchronous implementation dependent issues are handled by the asynchronous reference implementation used.

Distributed Programming

Naturally, Mandala extends asynchronous method invocation to asynchronous remote method invocation using its JACOb (Java Active Container of Objects) package which relies on RAMI and on the active container concept. Any objects can become remote and be used asynchronously. No more interface to implements (like in RMI or in Corba), no class to extend: only business code in your objects! Hence, your remote objects do not depend on any particular technology. JACOb uses the active container concept through the mandala.jacob.ActiveMap implementation. The concept is very simple. An active container is just a map which provides a call() method to invoke method on its stored objects.

Consider the example below:

        // Use JNDI to get a remote active map reference

        ActiveMap activeMap = null;

        String URL = "rmi://host/RMIActiveMap"; // may be LDAP

        // JNDI part
        try{
                javax.naming.InitialContext context = 
                        new javax.naming.InitialContext();
        	activeMap = (RemoteActiveMap) context.lookup(URL);
        }catch(NamingException ne) {
                ...
        }

        
        // A library returns an object I want to use remotely
        java.math.BigInteger bi = library.foo();

        // Get a global unique key 
        Object key = StoredObjectReference.getGlobalUniqueKey();

        // Insert 'bi' in the remote active map
        activeMap.put(key, bi);
	
        // Get a stored object reference on it
        StoredObjectReference sor = 
			StoredObjectReference.getInstance(activeMap, key);

        // Get an asynchronous proxy on this stored object
        jaya.java.math.BigInteger i = jaya.java.math.BigInteger.getInstance(sor);

        // 'i' is now a proxy on the stored object 'bi' in 'activeMap'
        assert sor == (StoredObjectReference) i.getAsychronousReference();
        assert activeMap.containsKey(sor.getKey()); 

        // Our 'BigInteger' is remote whereas it has not be designed for it!
        assert activeMap instanceof RemoteActiveMap;

        // Invoke a method remotely and asynchronously
        FutureClient future = i.rami_isProbablePrime(CERTAINTY);

        doSomethingElse();

        try{
                boolean isPrime = future.waitForResult();
        }catch(RemoteException e) {
                // Handle if necessary
        }

    
As you see, ActiveMap extends the java.util.Map interface - nothing to learn! RemoteActiveMap is just an ActiveMap remotely accessible. Then, we can insert an object into the active map (here a java.math.BigDecimal instance returned by a library) using the put() method. And then, the stored object is automatically remotely accessible through the active container it resides in! Asynchronous remote method invocation on stored objects are done using asynchronous proxies of the RAMI package. Note that if you are the creator of the stored object, things are simpler:

        // Create a stored object transparently
        
        // (A new key is automatically created)
        jaya.java.math.BigInteger i = 
                  new jaya.java.math.BigInteger("1234567890",
                                                new SORFactory(activeMap));

        // Invoke a method remotely and asynchronously
        FutureClient future = i.rami_isProbablePrime(CERTAINTY);

        doSomethingElse();

        try{
                boolean isPrime = future.waitForResult();
        }catch(TransportException te) {
                // Handle if necessary
        }

    

As you see, Mandala does not require objects to be special as it is commonly the case with other similar projects such as RMI (must implements the java.rmi.Remote interface and remote method must declare java.rmi.RemoteException in their throws clause) or EJB (must inherit SessionBean or EntityBean for example).

Hence, developers write their objects as usual, and then use the Mandala framework to invoke their methods asynchronously (using RAMI)and remotely.