Skip to content

Internationalization using the advanced RMI API

Thorben Kuck edited this page Sep 19, 2018 · 2 revisions

Java-server

We want to use the NetCom2 RMI API to utilize Internationalization. This example is relativly simple (since i do not want to torture you with Database-Stuff), but it will be just a prove of concept. Feel free to expand upon this example.

First of, let's define the interface, we want to use for this Communication:

import java.util.List;

public interface Internationalization {

    List<Language> getAvailableLanguages();

    List<String> getAvailableIdentifier();

    String getInLanguage(String id, Language language);

}

The Languages Object is a simple data-object, which looks like this:

import java.io.Serializable;

public class Language implements Serializable {

    private final String identifier;
    private final String name;

    public Language(String identifier, String name) {
        this.identifier = identifier;
        this.name = name;
    }

    public String identifier() {
        return identifier;
    }

    public String name() {
        return name;
    }

    @Override
    public String toString() {
        return identifier + "::" + name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Language)) return false;

        Language language = (Language) o;

        return identifier.equals(language.identifier) && name.equals(language.name);
    }

    @Override
    public int hashCode() {
        int result = identifier.hashCode();
        result = 31 * result + name.hashCode();
        return result;
    }
}

It just has a name, as well as an identifier for this language. In this example, they won't be needed. The only thing, they are needed for, is the equals method.

So, we need some sort of Table, to look up words. This can be a database, a properties file ore something. We use a hard-coded table and it looks like this:

import java.util.*;

public class ServerLanguageTable {

    private static final Language GERMAN = new Language("de", "Deutsch");
    private static final Language ENGLISH = new Language("en", "English");
    private static final List<Language> languages = Arrays.asList(GERMAN, ENGLISH);
    private static final List<String> identifier = new ArrayList<>();
    private static final Map<String, Map<Language, String>> table;
    private static final String UNKNOWN_WORD = "UNKNOWN";
    private static final String UNKNOWN_LANGUAGE = "UNKNOWN_LANGUAGE";

    static {
        table = new HashMap<>();
        Map<Language, String> greet = new HashMap<>();
        greet.put(GERMAN, "Hallo");
        greet.put(ENGLISH, "Hello");
        table.put("greet", greet);

        Map<Language, String> loggedIn = new HashMap<>();
        loggedIn.put(GERMAN, "Sie haben sich erfolgreich angemeldet!");
        loggedIn.put(ENGLISH, "You successfully logged in!");
        table.put("loggedIn", loggedIn);
		
        identifier.add("greet");
        identifier.add("loggedIn");
    }

    public List<Language> getAllAvailableLanguages() {
        return languages;
    }

    public String lookUp(String textIdentifier, Language language) {
        Map<Language, String> text = table.get(textIdentifier);
        if(text == null) {
            return UNKNOWN_WORD;
        }

        return text.getOrDefault(language, UNKNOWN_LANGUAGE);
    }

    public List<String> getAvailableIdentifier() {
        return new ArrayList<>(identifier);
    }
}

If it looks a bit weird, don't worry. We simply emulate a table with 2 Map Objects.

Before we create the actual Server, let's first create the ServerInternationalization class, that will use this LanguageTable:

import java.util.List;

private class ServerInternationalization implements Internationalization {

    ServerLanguageTable connector = new ServerLanguageTable();

    @Override
    public List<Language> getAvailableLanguages() {
        return connector.getAllAvailableLanguages();
    }

    @Override
    public List<String> getAvailableIdentifier() {
        return connector.getAvailableIdentifier();
    }

    @Override
    public String getInLanguage(String id, Language language) {
        return connector.lookUp(id, language);
    }
}

This class will simply delegate the calls. So, now lets actually create the Server, that will expose the RMI:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.github.thorbenkuck.netcom2.exceptions.ClientConnectionFailedException;
import com.github.thorbenkuck.netcom2.exceptions.StartFailedException;
import com.github.thorbenkuck.netcom2.network.server.RemoteObjectRegistration;
import com.github.thorbenkuck.netcom2.network.server.ServerStart;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class RMIServer {

    private final ServerStart serverStart;
    private final ExecutorService executorService = Executors.newSingleThreadExecutor();

    public RMIServer(int port) {
        this.serverStart = ServerStart.at(port);
    }

    public static void main(String[] args) {
        RMIServer rmiServer = new RMIServer(4444);
        rmiServer.run();
    }

    private void accept() {
        try {
            serverStart.acceptAllNextClients();
        } catch (ClientConnectionFailedException e) {
            e.printStackTrace();
        }
    }

    public void run() {
        RemoteObjectRegistration remoteObjectRegistration = RemoteObjectRegistration.open(serverStart);
        remoteObjectRegistration.register(new ServerInternationalization(), Internationalization.class);

        try {
            serverStart.launch();
        } catch (StartFailedException e) {
            e.printStackTrace();
        }

        executorService.submit(this::accept);
    }
}

This Server looks like most other examples. The only relevant part is, the first line of run, where we register the ServerInternatzionalization as an RemoteObject.

So, lastly, let's create the Client:

import com.github.thorbenkuck.netcom2.exceptions.StartFailedException;
import com.github.thorbenkuck.netcom2.network.client.ClientStart;
import com.github.thorbenkuck.netcom2.network.client.RemoteObjectFactory;

import java.util.ArrayList;
import java.util.List;

public class RMIClient {

    private final ClientStart clientStart;

    public RMIClient(String address, int port) {
        this.clientStart = ClientStart.at(address, port);
    }

    public static void main(String[] args) {
        RMIClient client = new RMIClient("localhost", 4444);
        client.run();
    }

    public void run() {
        try {
            clientStart.launch();
        } catch (StartFailedException e) {
            e.printStackTrace();
            return;
        }

        RemoteObjectFactory factory = RemoteObjectFactory.open(clientStart);

        Internationalization languages = factory.create(Internationalization.class, new LocalInternationalization());

        final List<Language> languagesList = languages.getAvailableLanguages();
        final List<String> identifiers = languages.getAvailableIdentifier();

        System.out.println(languagesList);
        System.out.println(identifiers);

        for(String id : identifiers) {
            for (Language language : languagesList) {
                System.out.println(language + "(" + id + ") = " + languages.getInLanguage(id, language));
            }
        }
    }

    private class LocalInternationalization implements Internationalization {

        @Override
        public List<Language> getAvailableLanguages() {
            return new ArrayList<>();
        }

        @Override
        public List<String> getAvailableIdentifier() {
            return new ArrayList<>();
        }

        @Override
        public String getInLanguage(String id, Language language) {
            return "No connection to Server";
        }
    }
}

Right here, we do not really do some Internationalization. We do a prove of work. What you would do, inside your code, where to use the identifier and (if needed) request the String from the RemoteObject.

So, what exactly is the advantage?
First of, the Client does not know, what is what. All he knows is, where to ask.
Second of, the advantage over simply "sending the Map" is, that if we update the table at the ServerSide, we do not have to worry about updating all Clients, that have already requested this Table.

Of course, it would be of benefit to send the Map at some point, so that we might use it, as a local fallback, if the Server disconnects.

Other programming languages

Since the protocol for the advanced RMI API allows for other languages to accept this RMI-requests, it is possible to hook up with other languages.

However, i will not provide an example just now. This will come in the near future.

Clone this wiki locally