Commit c5d717b1 authored by Carlos Galindo's avatar Carlos Galindo
Browse files

ErlConnection: merge with Launcher and add documentation.

parent bc8fe4b7
Loading
Loading
Loading
Loading
+80 −46
Original line number Diff line number Diff line
/*
 * e-Knife, a program slicing tool for Erlang based on the EDG
 * Copyright (c) 2021. David Insa, Sergio Pérez, Josep Silva, Salvador Tamarit.
 * EDG, a library to generate and slice Expression Dependence Graphs.
 * Copyright (c) 2021-2023. David Insa, Carlos Galindo, Sergio Pérez, Josep Silva, Salvador Tamarit.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
@@ -18,10 +18,7 @@

package eknife.erlang;

import com.ericsson.otp.erlang.OtpConnection;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpPeer;
import com.ericsson.otp.erlang.OtpSelf;
import com.ericsson.otp.erlang.*;
import eknife.config.Config;
import misc.util.Flusher;
import misc.util.Flusher.Output;
@@ -30,8 +27,17 @@ import java.io.File;
import java.io.IOException;
import java.util.UUID;

public class ErlConnection
{
/**
 * Handles the connection between Java and an Erlang process.
 * <br>
 * Important configuration settings: {@link #START_SERVER}, {@link #SERVER_COOKIE}, {@link #SERVER_NAME} and
 * {@link #RANDOMIZE_CLIENT_NAME}
 * <br>
 * General usage: {@code sendReceive(...)} handles everything, unless you want the connection to persist across
 * multiple calls. In that case you need to {@link #connect()} first. Otherwise, a connection is established and
 * closed for every RPC.
 */
public class ErlConnection {
	/** Starting the server can be time-consuming. For anything but standalone use, it might be better to start it
	 *  in the background and re-using the same server over different invocations of e-Knife. */
	private final static boolean START_SERVER = Boolean.parseBoolean(System.getProperty("erlServer.autoStart", "true"));
@@ -43,19 +49,28 @@ public class ErlConnection
	private static final boolean RANDOMIZE_CLIENT_NAME = Boolean.parseBoolean(System.getProperty("erlServer.randomizeClientName", "false"));
	private static final String defaultClientNodeName = "client";

	private static final ErlConnection instance = new ErlConnection();

	public static ErlConnection getInstance() {
		return instance;
	}

	private final Config config = Config.getConfig();

	private String serverNodeName;
	private OtpConnection connection;
	private Process serverProcess;

	public boolean isConnected()
	{
	public boolean isConnected() {
		return this.connection != null && this.connection.isConnected();
	}

	public void connect()
	{
	/**
	 * Connects or reconnects to an Erlang process, with a maximum of 10 retries if the connection fails.
	 * Behaviour is determined by Java properties, see {@link ErlConnection class JavaDoc}.
	 * @throws RuntimeException When the connection cannot be established even after retrying.
	 */
	public void connect() {
		if (this.isConnected())
			this.disconnect();

@@ -63,8 +78,7 @@ public class ErlConnection
		Exception lastException = null;

		for (int id = 0; id < 10 && !connected; id++)
			try
			{
			try {
				String clientNodeName = RANDOMIZE_CLIENT_NAME ? UUID.randomUUID().toString() : defaultClientNodeName + id;
				this.serverNodeName = START_SERVER ? SERVER_NAME + id : SERVER_NAME;
				if (START_SERVER)
@@ -73,67 +87,87 @@ public class ErlConnection
				OtpPeer serverNode = new OtpPeer(this.serverNodeName + "@localhost");
				this.connection = clientNode.connect(serverNode);
				connected = true;
			}
			catch (Exception e)
			{
			} catch (Exception e) {
				lastException = e;
				this.closeServer();
			}
		if (!connected)
			throw new RuntimeException("Could not create and connect to an erlang process!", lastException);
	}
	public void disconnect()
	{

	/**
	 * Sever the connection between Java and Erlang, and shut the Erlang process
	 * down if it was started by this object.
	 * @throws RuntimeException If not connected or if an error occurs while closing and shutting the process.
	 */
	private void disconnect() {
		if (!this.isConnected())
			throw new RuntimeException("The connection is not established");

		try
		{
		try {
			if (this.connection != null)
				this.connection.close();
			this.closeServer();
		}
		catch (Exception e)
		{
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	private void openServer() throws Exception {
		// Start an `erl` process with a shell, so that it has some output, and we can
		// measure using Flusher that it has started.

	/**
	 * Start an Erlang process to act as a server for our RPCs.
	 * @throws IOException Caused by {@link ProcessBuilder#start() starting} the process.
	 */
	private void openServer() throws IOException {
		// Skip the -noshell argument to produce some output and be able to know when the Erlang process has started.
		final File scriptsFile = this.config.getScriptsFile();
		serverProcess = new ProcessBuilder("erl", "-pa", scriptsFile.getAbsolutePath(),
				"-name", this.serverNodeName + "@localhost", "-setcookie", SERVER_COOKIE).start();
		// Wait until the output of the process has been consumed (initial shell prompt).
		final Flusher flusher = new Flusher(this.serverProcess, true, true);
		flusher.start();
		while (flusher.getOutput(Output.Standard).isEmpty() && flusher.getOutput(Output.Error).isEmpty());
	}
	private void closeServer()
	{

	/**
	 * Kill the Erlang process.
	 */
	private void closeServer() {
		if (this.serverProcess != null)
			this.serverProcess.destroy();
	}

	public void send(String module, String function, OtpErlangObject[] message)
	{
		try
		{
			this.connection.sendRPC(module, function, message);
		}
		catch (IOException e)
		{
			throw new RuntimeException(e);
	public OtpErlangObject sendReceive(String module, String function, String... arguments) {
		return sendReceive(module, function, string2atom(arguments));
	}
	}
	public OtpErlangObject receive()
	{
		try
		{

	/**
	 * Perform a remote process call (RPC) to the currently connected Erlang process.
	 * @param module     Module of the RPC
	 * @param function   Function of the RPC, within the given module.
	 * @param arguments  List of arguments passed to the function. Must match the arity of the function being called.
	 * @return The result returned by the RPC.
	 * @throws RuntimeException For connection and deserialization errors (or if the connection is inactive).
	 */
	public OtpErlangObject sendReceive(String module, String function, OtpErlangObject... arguments) {
		boolean connected = isConnected();
		if (!connected)
			connect();
		try {
			this.connection.sendRPC(module, function, arguments);
			return this.connection.receiveMsg().getMsg();
		}
		catch (Exception e)
		{
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			if (!connected)
				disconnect();
		}
	}

	/** Convert an array of strings to Erlang atoms. */
	private OtpErlangObject[] string2atom(String... args) {
		final OtpErlangObject[] arguments = new OtpErlangObject[args.length];
		for (int argIndex = 0; argIndex < args.length; argIndex++)
			arguments[argIndex] = new OtpErlangAtom(args[argIndex]);
		return arguments;
	}
}
+7 −14
Original line number Diff line number Diff line
/*
 * e-Knife, a program slicing tool for Erlang based on the EDG
 * Copyright (c) 2021. David Insa, Sergio Pérez, Josep Silva, Salvador Tamarit.
 * EDG, a library to generate and slice Expression Dependence Graphs.
 * Copyright (c) 2021-2023. David Insa, Carlos Galindo, Sergio Pérez, Josep Silva, Salvador Tamarit.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
@@ -18,21 +18,15 @@

package eknife.erlang;

import java.io.File;
import java.util.*;

import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangList;
import com.ericsson.otp.erlang.OtpErlangLong;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangString;
import com.ericsson.otp.erlang.OtpErlangTuple;

import com.ericsson.otp.erlang.*;
import edg.LDASTNodeInfo;
import edg.graph.EDG;
import edg.graph.Node;
import eknife.config.Config;

import java.io.File;
import java.util.*;

public class ErlangCodeFactory
{
	/********************************************************************************************************************************/
@@ -49,8 +43,7 @@ public class ErlangCodeFactory
		final ErlangCodeFactory erlangFactory = new ErlangCodeFactory(edg, slice);
		final OtpErlangList outDir = new OtpErlangList(outputFile.getAbsolutePath());
		final OtpErlangList asts = erlangFactory.generate();
		final Launcher launcher = Launcher.getLauncher();
		launcher.launch("saver", "save", outDir, asts);
		ErlConnection.getInstance().sendReceive("saver", "save", outDir, asts);
	}

	/********************************************************************************************************************************/
+3 −4
Original line number Diff line number Diff line
/*
 * e-Knife, a program slicing tool for Erlang based on the EDG
 * Copyright (c) 2021. David Insa, Sergio Pérez, Josep Silva, Salvador Tamarit.
 * EDG, a library to generate and slice Expression Dependence Graphs.
 * Copyright (c) 2021-2023. David Insa, Carlos Galindo, Sergio Pérez, Josep Silva, Salvador Tamarit.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
@@ -43,8 +43,7 @@ public class ErlangLASTFactory extends LASTFactory
	}
	public static LAST createLAST(String sourcePath, boolean generateArcs)
	{
		final Launcher launcher = Launcher.getLauncher();
		final OtpErlangObject response = launcher.launch("ast", "getASTs", sourcePath);
		final OtpErlangObject response = ErlConnection.getInstance().sendReceive("ast", "getASTs", sourcePath);
		final OtpErlangTuple tuple = (OtpErlangTuple) response;
		if (!(tuple.elementAt(1) instanceof OtpErlangList))
			throw new RuntimeException("The erlang program could not be loaded: " + tuple.elementAt(1).toString());
+0 −83
Original line number Diff line number Diff line
/*
 * e-Knife, a program slicing tool for Erlang based on the EDG
 * Copyright (c) 2021. David Insa, Sergio Pérez, Josep Silva, Salvador Tamarit.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package eknife.erlang;

import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangObject;

public class Launcher
{
	private static final Launcher launcher = new Launcher();
	public static Launcher getLauncher()
	{
		return Launcher.launcher;
	}

	private final ErlConnection erlConnection = new ErlConnection();

	private Launcher()
	{

	}

	public void open()
	{
		this.erlConnection.connect();
	}
	public void close()
	{
		this.erlConnection.disconnect();
	}

	public OtpErlangObject launch(String module, String function)
	{
		final OtpErlangObject[] arguments = new OtpErlangObject[0];

		return this.launch(module, function, arguments);
	}
	public OtpErlangObject launch(String module, String function, String... args)
	{
		final OtpErlangObject[] arguments = this.getArgs(args);

		return this.launch(module, function, arguments);
	}
	public OtpErlangObject launch(String module, String function, OtpErlangObject... args)
	{
		final boolean connected = this.erlConnection.isConnected();

		if (!connected)
			this.erlConnection.connect();
		this.erlConnection.send(module, function, args);
		final OtpErlangObject response = this.erlConnection.receive();
		if (!connected)
			this.erlConnection.disconnect();

		return response;
	}
	private OtpErlangObject[] getArgs(String... args)
	{
		final int argsLength = args.length;
		final OtpErlangObject[] arguments = new OtpErlangObject[argsLength];

		for (int argIndex = 0; argIndex < argsLength; argIndex++)
			arguments[argIndex] = new OtpErlangAtom(args[argIndex]);

		return arguments;
	}
}
 No newline at end of file