Java and Rust Gurus - feedback on converting a Java module .

People,

I am making a little progress learning Rust and the success of this "concept exercise":

has prompted me to continue the effort. I have some old (paid for) Java chatbot code and, some years ago I thought of getting it re-written entirely in Rust but real-world issues demanded attention and I never made any more progress with the idea. Now that I am attempting to learn Rust myself, it occurred to me that, as an exercise mostly, I could just re-write a small module of the Java code instead of the whole application. I attach the module in question below but I think my options are now:

  1. If the conversion to Rust is sensibly doable, try and do the work myself - however, I am entirely unenthusiastic about having to learn Java as well so this may not be a good use of my time in terms of making good progress learning Rust . .

  2. If the conversion to Rust is sensibly doable but the effort is really beyond me, so just pay a guru to do it - if it is not going to break the bank . . This would be nice but not critical . .

  3. Since it is mostly an exercise, although the conversion might be doable, it might still be a waste of time so I should concentrate my Rust learning elsewhere . .

Feedback appreciated!

Regards,

Phil.

package com.thisiscool.honesty.server;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

import com.thisiscool.honesty.common.PropertiesManager;
import com.thisiscool.honesty.common.Sex;
import com.thisiscool.honesty.knowledge.Knowledge;
import com.thisiscool.honesty.model.HModel;
import com.thisiscool.honesty.model.types.HInstance;
import com.thisiscool.honesty.model.types.HValue;
import com.thisiscool.honesty.process.ConversationSessionData;
import com.thisiscool.honesty.process.MainLoop;
import com.thisiscool.honesty.process.NosyGoal;
import com.thisiscool.honesty.subject.SubjectsManager;

public class HonestyServer
{
public static void main(String[] args) throws IOException
{
	PropertiesManager pm = PropertiesManager.instance();

	int port = pm.getIntApplicationProperty("app.honesty.port");
	int poolSize = pm.getIntApplicationProperty("app.honesty.poolsize");

	HonestyServer hs = new HonestyServer(port, poolSize);
	hs.run();
}
	
public HonestyServer(int port, int poolSize) throws IOException
{
    serverSocket = new ServerSocket(port);
    pool = 
        new ThreadPoolExecutor(1, poolSize,
                2, TimeUnit.MINUTES,
                new LinkedBlockingQueue<Runnable>());
}
	
public final void run()
{
	try
	{
		mainLoop = new MainLoop();
		mainLoop.init();
		
		try
		{
			System.out.println("Honesty Server ready for action!");
			for (;;)
			{
				pool.execute(new Handler(serverSocket.accept()));
			}
		}
		catch (IOException ex)
		{
			pool.shutdown();
		}
		finally
		{
			serverSocket.close();
		}
	}
	catch (Exception e)
	{
		e.printStackTrace();
	}
}

// private //
private static final String SHUTDOWN = "!@#$%^*!@^T$%&#@SHUTDOWN";
private static final String ERROR = "Malformed request.";

@SuppressWarnings("unchecked")
private static String toJSONString(Collection<HInstance> instances)
{
	JSONArray result = new JSONArray();
	for (HInstance instance : instances)
	{
		Map<String,String> inst = new HashMap<String,String>();
		inst.put("uuid", instance.getUUID());
		inst.put("name", instance.getName());
		inst.put("friendly_name", instance.getFriendlyName());
		
		HInstance parent = instance.getParent();
		if (parent == null)
			inst.put("parent", "null");
		else
			inst.put("parent", parent.getUUID());
		
		result.add(inst);
	}
	return result.toJSONString();
}

private final ServerSocket serverSocket;
private final ExecutorService pool;
private MainLoop mainLoop;
private SessionManager sessionManager = new SessionManager();

private String handle(String request)
{
	JSONObject input = (JSONObject) JSONValue.parse(request);
	if (input == null)
		return ERROR;
	
	String sessionId = (String) input.get("session_id");
	String message = (String) input.get("message");
	String subjectName = null;
	
	if (input.get("commands") != null)
	{
		JSONObject cmds = (JSONObject) input.get("commands");
		if (cmds != null)
		{
			String name = (String) cmds.get("name");
			if (name != null)
			{
				subjectName = name;
				if (subjectName.toLowerCase().startsWith("future "))
					subjectName = subjectName.substring(7);
			}
		}
	}
	
	boolean create = false;
	if (sessionId.startsWith("@"))
	{
		create = true;
		sessionId = sessionId.substring(1);
	}

	if (message!=null && message.equals(SHUTDOWN))
	{
		sessionManager.dispose(sessionId);
		return "OK";
	}
	else
	{
		ConversationSessionData csd =
			sessionManager.getSessionDataFor(sessionId, create, mainLoop);
		
		if (csd == null)
			return "@@BAD SESSION";
		
		String result = "@@BAD RESPONSE";
		synchronized (csd)
		{
			// new name?
			if (subjectName != null)
			{
				SubjectsManager sm = csd.getSubjectsManager();
				String current = sm.getSubjectName();
				if (current==null || !current.equalsIgnoreCase(subjectName))
					sm.setSubjectName(subjectName);
			}
			
			if (input.get("chip_cmd") != null)
			{
				result = handleChipCommand(input, csd);
			}
			else
			{
				result = mainLoop.submit(message, csd);
				sessionManager.save(sessionId, csd);
			}
		}
		return result;
	}
}

private String handleChipCommand(JSONObject input, ConversationSessionData csd)
{
	String chipCmd = (String) input.get("chip_cmd");
	
	String result = null;
	if (chipCmd.equals("retrieve_instances"))
	{
		result = handleRetrieveInstances(input,csd);
	}
	else if (chipCmd.equals("set_current_instance"))
	{
		result = handleSetCurrentInstance(input,csd);
	}
	else if (chipCmd.equals("create_instance"))
	{
		result = handleCreateInstance(input,csd);
	}
	
	if (result == null)
		return "{\"error\": \"Unknown command\"}";
	else
		return result;
}

private String handleRetrieveInstances(JSONObject input, ConversationSessionData csd)
{
	Set<HInstance> instances = csd.getExecutionEngine().getInstances();
	return toJSONString(instances);
}

private String handleSetCurrentInstance(JSONObject input, ConversationSessionData csd)
{
	String uuid = (String) input.get("uuid");
	
	// Does it exist?
	String result = null;
	HInstance instance = csd.getExecutionEngine().findByUUID(uuid);
	if (instance == null)
	{
		result = "{\"error\": \"No such instance\"}";
	}
	else
	{
		csd.getExecutionEngine().setFocusedInstance(instance);
		result = "{\"status\": \"success\"}";
	}
	return result;
}

private String handleCreateInstance(JSONObject input, ConversationSessionData csd)
{
	String parentUUID = (String) input.get("parent_uuid");
	String type = (String) input.get("type");
	String name = (String) input.get("name");
	
	String result = null;
	HInstance parent = csd.getExecutionEngine().findByUUID(parentUUID);
	if (parent == null)
	{
		result = "{\"error\": \"No such parent instance\"}";
	}
	else
	{
		HModel model = parent.getType().getModel();
		String field = NosyGoal.findFieldFor(model, type);
		if (field == null)
		{
			result = "{\"error\": \"Unsupported child instance type: "+type+"\"}";
		}
		else
		{
			Sex sex = Knowledge.getSexFor(csd.getGoalManager(), type);
			HInstance[] instances = parent.addQualified(field, 1, type, sex);
			
			if (name != null)
			{
				for (HInstance instance : instances)
				{
					instance.set("name", name);
				}
			}
			
			// adjust the backpointers for mother or father depending on the sex of the parent
			if (type.equals("son") || type.equals("daughter"))
			{
				for (HInstance instance : instances)
				{
					HValue hval = parent.get("sex");
					if (hval != null)
					{
						Sex parentSex = Sex.UNKNOWN;
						
						String current = hval.toString();
						if (current.equals("male"))
							parentSex = Sex.MALE;
						else if (current.equals("female"))
							parentSex = Sex.FEMALE;
						
						switch (parentSex)
						{
						case MALE:
							instance.getEngine().remove((HInstance) instance.get("father"));
							instance.set("father", parent);
							break;
						case FEMALE:
							instance.getEngine().remove((HInstance) instance.get("mother"));
							instance.set("mother", parent);
							break;
						}
					}
				}
			}
			
			result = toJSONString(Arrays.asList(instances));
		}
	}
	return result;
}

////////////////////////////////////////////////////////////////
// inner class Handler

private class Handler implements Runnable
{
// Runnable overrides
public final void run()
{
	try
	{
		PrintStream strmOut = new PrintStream(
				new BufferedOutputStream(socket
						.getOutputStream()));
		BufferedReader rdrIn = new BufferedReader(
				new InputStreamReader(socket
						.getInputStream()));

		// Receive request
		String request = rdrIn.readLine();
//@LOG@		System.out.println("Got " + request);

		// Determine and send response
		String reply = handle(request);
		strmOut.println(reply);
		strmOut.println("@@END_OF_REPLY");
//@LOG@		System.out.println("  ...sent reply: "+reply);

		strmOut.close();
		rdrIn.close();
	}
	catch (IOException e)
	{
		e.printStackTrace();
	}
	finally
	{
		try
		{
			socket.close();
		}
		catch (IOException e)
		{}
	}
}
 
// "internal" //
Handler(Socket socket)
{
	this.socket = socket;
}
    
// private //
private final Socket socket;
}
// end inner class Handler
////////////////////////////////////////////////////////////////

}

Hmm... unless whatever this code does is of huge value and there is some pressing need to rewrite it, the idea of translating it from one language that you don't know to another language that you don't know sounds like a nightmare. A gigantic effort that is likely not a good way to learn Rust and will result in a mess of a solution.

If there is a huge value and a pressing need in this it might be wise to not concentrate on the existing Java implementation's actual code but rather what it does. If you can nail down a specification of it's expected behavior you can then implement that in Rust. The resulting code may not look anything like the original Java.

Of course that is predicated on becoming reasonably fluent in Rust first.

Years I ago was was engaged in reimplementing many thousands of lines of assembler of an embedded systems software in C so that it could be moved to a different CPU architecture. And this is the approach I took. I could have slavishly transcribed that mess of assembler spaghetti line by line into huge C functions full of gotos. But that would have been a mess and mostly miss the point of the exercise. Stepping back and teasing out the requirement from the original code and then implementing that in C gave a nice maintainable, extensible result.