diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/jinterface/java_src/com | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/jinterface/java_src/com')
53 files changed, 12747 insertions, 0 deletions
diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java new file mode 100644 index 0000000000..ab0b299bf9 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractConnection.java @@ -0,0 +1,1361 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +package com.ericsson.otp.erlang; + +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.util.Random; + +/** + * Maintains a connection between a Java process and a remote Erlang, Java or C + * node. The object maintains connection state and allows data to be sent to and + * received from the peer. + * + * <p> + * This abstract class provides the neccesary methods to maintain the actual + * connection and encode the messages and headers in the proper format according + * to the Erlang distribution protocol. Subclasses can use these methods to + * provide a more or less transparent communication channel as desired. + * </p> + * + * <p> + * Note that no receive methods are provided. Subclasses must provide methods + * for message delivery, and may implement their own receive methods. + * <p> + * + * <p> + * If an exception occurs in any of the methods in this class, the connection + * will be closed and must be reopened in order to resume communication with the + * peer. This will be indicated to the subclass by passing the exception to its + * delivery() method. + * </p> + * + * <p> + * The System property OtpConnection.trace can be used to change the initial + * trace level setting for all connections. Normally the initial trace level is + * 0 and connections are not traced unless {@link #setTraceLevel + * setTraceLevel()} is used to change the setting for a particular connection. + * OtpConnection.trace can be used to turn on tracing by default for all + * connections. + * </p> + */ +public abstract class AbstractConnection extends Thread { + protected static final int headerLen = 2048; // more than enough + + protected static final byte passThrough = (byte) 0x70; + protected static final byte version = (byte) 0x83; + + // Erlang message header tags + protected static final int linkTag = 1; + protected static final int sendTag = 2; + protected static final int exitTag = 3; + protected static final int unlinkTag = 4; + protected static final int nodeLinkTag = 5; + protected static final int regSendTag = 6; + protected static final int groupLeaderTag = 7; + protected static final int exit2Tag = 8; + + protected static final int sendTTTag = 12; + protected static final int exitTTTag = 13; + protected static final int regSendTTTag = 16; + protected static final int exit2TTTag = 18; + + // MD5 challenge messsage tags + protected static final int ChallengeReply = 'r'; + protected static final int ChallengeAck = 'a'; + protected static final int ChallengeStatus = 's'; + + private volatile boolean done = false; + + protected boolean connected = false; // connection status + protected Socket socket; // communication channel + protected OtpPeer peer; // who are we connected to + protected OtpLocalNode self; // this nodes id + String name; // local name of this connection + + protected boolean cookieOk = false; // already checked the cookie for this + // connection + protected boolean sendCookie = true; // Send cookies in messages? + + // tracelevel constants + protected int traceLevel = 0; + + protected static int defaultLevel = 0; + protected static int sendThreshold = 1; + protected static int ctrlThreshold = 2; + protected static int handshakeThreshold = 3; + + protected static Random random = null; + + private int flags = 0; + + static { + // trace this connection? + final String trace = System.getProperties().getProperty( + "OtpConnection.trace"); + try { + if (trace != null) { + defaultLevel = Integer.valueOf(trace).intValue(); + } + } catch (final NumberFormatException e) { + defaultLevel = 0; + } + random = new Random(); + } + + // private AbstractConnection() { + // } + + /** + * Accept an incoming connection from a remote node. Used by {@link + * OtpSelf#accept() OtpSelf.accept()} to create a connection based on data + * received when handshaking with the peer node, when the remote node is the + * connection intitiator. + * + * @exception java.io.IOException if it was not possible to connect to the + * peer. + * + * @exception OtpAuthException if handshake resulted in an authentication + * error + */ + protected AbstractConnection(final OtpLocalNode self, final Socket s) + throws IOException, OtpAuthException { + this.self = self; + peer = new OtpPeer(); + socket = s; + + socket.setTcpNoDelay(true); + + traceLevel = defaultLevel; + setDaemon(true); + + if (traceLevel >= handshakeThreshold) { + System.out.println("<- ACCEPT FROM " + s.getInetAddress() + ":" + + s.getPort()); + } + + // get his info + recvName(peer); + + // now find highest common dist value + if (peer.proto != self.proto || self.distHigh < peer.distLow + || self.distLow > peer.distHigh) { + close(); + throw new IOException( + "No common protocol found - cannot accept connection"); + } + // highest common version: min(peer.distHigh, self.distHigh) + peer.distChoose = peer.distHigh > self.distHigh ? self.distHigh + : peer.distHigh; + + doAccept(); + name = peer.node(); + } + + /** + * Intiate and open a connection to a remote node. + * + * @exception java.io.IOException if it was not possible to connect to the + * peer. + * + * @exception OtpAuthException if handshake resulted in an authentication + * error. + */ + protected AbstractConnection(final OtpLocalNode self, final OtpPeer other) + throws IOException, OtpAuthException { + peer = other; + this.self = self; + socket = null; + int port; + + traceLevel = defaultLevel; + setDaemon(true); + + // now get a connection between the two... + port = OtpEpmd.lookupPort(peer); + + // now find highest common dist value + if (peer.proto != self.proto || self.distHigh < peer.distLow + || self.distLow > peer.distHigh) { + throw new IOException("No common protocol found - cannot connect"); + } + + // highest common version: min(peer.distHigh, self.distHigh) + peer.distChoose = peer.distHigh > self.distHigh ? self.distHigh + : peer.distHigh; + + doConnect(port); + + name = peer.node(); + connected = true; + } + + /** + * Deliver communication exceptions to the recipient. + */ + public abstract void deliver(Exception e); + + /** + * Deliver messages to the recipient. + */ + public abstract void deliver(OtpMsg msg); + + /** + * Send a pre-encoded message to a named process on a remote node. + * + * @param dest + * the name of the remote process. + * @param payload + * the encoded message to send. + * + * @exception java.io.IOException + * if the connection is not active or a communication error + * occurs. + */ + protected void sendBuf(final OtpErlangPid from, final String dest, + final OtpOutputStream payload) throws IOException { + if (!connected) { + throw new IOException("Not connected"); + } + final OtpOutputStream header = new OtpOutputStream(headerLen); + + // preamble: 4 byte length + "passthrough" tag + version + header.write4BE(0); // reserve space for length + header.write1(passThrough); + header.write1(version); + + // header info + header.write_tuple_head(4); + header.write_long(regSendTag); + header.write_any(from); + if (sendCookie) { + header.write_atom(self.cookie()); + } else { + header.write_atom(""); + } + header.write_atom(dest); + + // version for payload + header.write1(version); + + // fix up length in preamble + header.poke4BE(0, header.size() + payload.size() - 4); + + do_send(header, payload); + } + + /** + * Send a pre-encoded message to a process on a remote node. + * + * @param dest + * the Erlang PID of the remote process. + * @param msg + * the encoded message to send. + * + * @exception java.io.IOException + * if the connection is not active or a communication error + * occurs. + */ + protected void sendBuf(final OtpErlangPid from, final OtpErlangPid dest, + final OtpOutputStream payload) throws IOException { + if (!connected) { + throw new IOException("Not connected"); + } + final OtpOutputStream header = new OtpOutputStream(headerLen); + + // preamble: 4 byte length + "passthrough" tag + version + header.write4BE(0); // reserve space for length + header.write1(passThrough); + header.write1(version); + + // header info + header.write_tuple_head(3); + header.write_long(sendTag); + if (sendCookie) { + header.write_atom(self.cookie()); + } else { + header.write_atom(""); + } + header.write_any(dest); + + // version for payload + header.write1(version); + + // fix up length in preamble + header.poke4BE(0, header.size() + payload.size() - 4); + + do_send(header, payload); + } + + /* + * Send an auth error to peer because he sent a bad cookie. The auth error + * uses his cookie (not revealing ours). This is just like send_reg + * otherwise + */ + private void cookieError(final OtpLocalNode local, + final OtpErlangAtom cookie) throws OtpAuthException { + try { + final OtpOutputStream header = new OtpOutputStream(headerLen); + + // preamble: 4 byte length + "passthrough" tag + version + header.write4BE(0); // reserve space for length + header.write1(passThrough); + header.write1(version); + + header.write_tuple_head(4); + header.write_long(regSendTag); + header.write_any(local.createPid()); // disposable pid + header.write_atom(cookie.atomValue()); // important: his cookie, + // not mine... + header.write_atom("auth"); + + // version for payload + header.write1(version); + + // the payload + + // the no_auth message (copied from Erlang) Don't change this + // (Erlang will crash) + // {$gen_cast, {print, "~n** Unauthorized cookie ~w **~n", + // [foo@aule]}} + final OtpErlangObject[] msg = new OtpErlangObject[2]; + final OtpErlangObject[] msgbody = new OtpErlangObject[3]; + + msgbody[0] = new OtpErlangAtom("print"); + msgbody[1] = new OtpErlangString("~n** Bad cookie sent to " + local + + " **~n"); + // Erlang will crash and burn if there is no third argument here... + msgbody[2] = new OtpErlangList(); // empty list + + msg[0] = new OtpErlangAtom("$gen_cast"); + msg[1] = new OtpErlangTuple(msgbody); + + final OtpOutputStream payload = new OtpOutputStream( + new OtpErlangTuple(msg)); + + // fix up length in preamble + header.poke4BE(0, header.size() + payload.size() - 4); + + try { + do_send(header, payload); + } catch (final IOException e) { + } // ignore + } finally { + close(); + } + throw new OtpAuthException("Remote cookie not authorized: " + + cookie.atomValue()); + } + + // link to pid + + /** + * Create a link between the local node and the specified process on the + * remote node. If the link is still active when the remote process + * terminates, an exit signal will be sent to this connection. Use + * {@link #sendUnlink unlink()} to remove the link. + * + * @param dest + * the Erlang PID of the remote process. + * + * @exception java.io.IOException + * if the connection is not active or a communication error + * occurs. + */ + protected void sendLink(final OtpErlangPid from, final OtpErlangPid dest) + throws IOException { + if (!connected) { + throw new IOException("Not connected"); + } + final OtpOutputStream header = new OtpOutputStream(headerLen); + + // preamble: 4 byte length + "passthrough" tag + header.write4BE(0); // reserve space for length + header.write1(passThrough); + header.write1(version); + + // header + header.write_tuple_head(3); + header.write_long(linkTag); + header.write_any(from); + header.write_any(dest); + + // fix up length in preamble + header.poke4BE(0, header.size() - 4); + + do_send(header); + } + + /** + * Remove a link between the local node and the specified process on the + * remote node. This method deactivates links created with {@link #sendLink + * link()}. + * + * @param dest + * the Erlang PID of the remote process. + * + * @exception java.io.IOException + * if the connection is not active or a communication error + * occurs. + */ + protected void sendUnlink(final OtpErlangPid from, final OtpErlangPid dest) + throws IOException { + if (!connected) { + throw new IOException("Not connected"); + } + final OtpOutputStream header = new OtpOutputStream(headerLen); + + // preamble: 4 byte length + "passthrough" tag + header.write4BE(0); // reserve space for length + header.write1(passThrough); + header.write1(version); + + // header + header.write_tuple_head(3); + header.write_long(unlinkTag); + header.write_any(from); + header.write_any(dest); + + // fix up length in preamble + header.poke4BE(0, header.size() - 4); + + do_send(header); + } + + /* used internally when "processes" terminate */ + protected void sendExit(final OtpErlangPid from, final OtpErlangPid dest, + final OtpErlangObject reason) throws IOException { + sendExit(exitTag, from, dest, reason); + } + + /** + * Send an exit signal to a remote process. + * + * @param dest + * the Erlang PID of the remote process. + * @param reason + * an Erlang term describing the exit reason. + * + * @exception java.io.IOException + * if the connection is not active or a communication error + * occurs. + */ + protected void sendExit2(final OtpErlangPid from, final OtpErlangPid dest, + final OtpErlangObject reason) throws IOException { + sendExit(exit2Tag, from, dest, reason); + } + + private void sendExit(final int tag, final OtpErlangPid from, + final OtpErlangPid dest, final OtpErlangObject reason) + throws IOException { + if (!connected) { + throw new IOException("Not connected"); + } + final OtpOutputStream header = new OtpOutputStream(headerLen); + + // preamble: 4 byte length + "passthrough" tag + header.write4BE(0); // reserve space for length + header.write1(passThrough); + header.write1(version); + + // header + header.write_tuple_head(4); + header.write_long(tag); + header.write_any(from); + header.write_any(dest); + header.write_any(reason); + + // fix up length in preamble + header.poke4BE(0, header.size() - 4); + + do_send(header); + } + + @Override + public void run() { + if (!connected) { + deliver(new IOException("Not connected")); + return; + } + + final byte[] lbuf = new byte[4]; + OtpInputStream ibuf; + OtpErlangObject traceobj; + int len; + final byte[] tock = { 0, 0, 0, 0 }; + + try { + receive_loop: while (!done) { + // don't return until we get a real message + // or a failure of some kind (e.g. EXIT) + // read length and read buffer must be atomic! + tick_loop: do { + // read 4 bytes - get length of incoming packet + // socket.getInputStream().read(lbuf); + readSock(socket, lbuf); + ibuf = new OtpInputStream(lbuf, flags); + len = ibuf.read4BE(); + + // received tick? send tock! + if (len == 0) { + synchronized (this) { + socket.getOutputStream().write(tock); + } + } + + } while (len == 0); // tick_loop + + // got a real message (maybe) - read len bytes + final byte[] tmpbuf = new byte[len]; + // i = socket.getInputStream().read(tmpbuf); + readSock(socket, tmpbuf); + ibuf = new OtpInputStream(tmpbuf, flags); + + if (ibuf.read1() != passThrough) { + break receive_loop; + } + + // got a real message (really) + OtpErlangObject reason = null; + OtpErlangAtom cookie = null; + OtpErlangObject tmp = null; + OtpErlangTuple head = null; + OtpErlangAtom toName; + OtpErlangPid to; + OtpErlangPid from; + int tag; + + // decode the header + tmp = ibuf.read_any(); + if (!(tmp instanceof OtpErlangTuple)) { + break receive_loop; + } + + head = (OtpErlangTuple) tmp; + if (!(head.elementAt(0) instanceof OtpErlangLong)) { + break receive_loop; + } + + // lets see what kind of message this is + tag = (int) ((OtpErlangLong) head.elementAt(0)).longValue(); + + switch (tag) { + case sendTag: // { SEND, Cookie, ToPid } + case sendTTTag: // { SEND, Cookie, ToPid, TraceToken } + if (!cookieOk) { + // we only check this once, he can send us bad cookies + // later if he likes + if (!(head.elementAt(1) instanceof OtpErlangAtom)) { + break receive_loop; + } + cookie = (OtpErlangAtom) head.elementAt(1); + if (sendCookie) { + if (!cookie.atomValue().equals(self.cookie())) { + cookieError(self, cookie); + } + } else { + if (!cookie.atomValue().equals("")) { + cookieError(self, cookie); + } + } + cookieOk = true; + } + + if (traceLevel >= sendThreshold) { + System.out.println("<- " + headerType(head) + " " + + head); + + /* show received payload too */ + ibuf.mark(0); + traceobj = ibuf.read_any(); + + if (traceobj != null) { + System.out.println(" " + traceobj); + } else { + System.out.println(" (null)"); + } + ibuf.reset(); + } + + to = (OtpErlangPid) head.elementAt(2); + + deliver(new OtpMsg(to, ibuf)); + break; + + case regSendTag: // { REG_SEND, FromPid, Cookie, ToName } + case regSendTTTag: // { REG_SEND, FromPid, Cookie, ToName, + // TraceToken } + if (!cookieOk) { + // we only check this once, he can send us bad cookies + // later if he likes + if (!(head.elementAt(2) instanceof OtpErlangAtom)) { + break receive_loop; + } + cookie = (OtpErlangAtom) head.elementAt(2); + if (sendCookie) { + if (!cookie.atomValue().equals(self.cookie())) { + cookieError(self, cookie); + } + } else { + if (!cookie.atomValue().equals("")) { + cookieError(self, cookie); + } + } + cookieOk = true; + } + + if (traceLevel >= sendThreshold) { + System.out.println("<- " + headerType(head) + " " + + head); + + /* show received payload too */ + ibuf.mark(0); + traceobj = ibuf.read_any(); + + if (traceobj != null) { + System.out.println(" " + traceobj); + } else { + System.out.println(" (null)"); + } + ibuf.reset(); + } + + from = (OtpErlangPid) head.elementAt(1); + toName = (OtpErlangAtom) head.elementAt(3); + + deliver(new OtpMsg(from, toName.atomValue(), ibuf)); + break; + + case exitTag: // { EXIT, FromPid, ToPid, Reason } + case exit2Tag: // { EXIT2, FromPid, ToPid, Reason } + if (head.elementAt(3) == null) { + break receive_loop; + } + if (traceLevel >= ctrlThreshold) { + System.out.println("<- " + headerType(head) + " " + + head); + } + + from = (OtpErlangPid) head.elementAt(1); + to = (OtpErlangPid) head.elementAt(2); + reason = head.elementAt(3); + + deliver(new OtpMsg(tag, from, to, reason)); + break; + + case exitTTTag: // { EXIT, FromPid, ToPid, TraceToken, Reason } + case exit2TTTag: // { EXIT2, FromPid, ToPid, TraceToken, + // Reason + // } + // as above, but bifferent element number + if (head.elementAt(4) == null) { + break receive_loop; + } + if (traceLevel >= ctrlThreshold) { + System.out.println("<- " + headerType(head) + " " + + head); + } + + from = (OtpErlangPid) head.elementAt(1); + to = (OtpErlangPid) head.elementAt(2); + reason = head.elementAt(4); + + deliver(new OtpMsg(tag, from, to, reason)); + break; + + case linkTag: // { LINK, FromPid, ToPid} + case unlinkTag: // { UNLINK, FromPid, ToPid} + if (traceLevel >= ctrlThreshold) { + System.out.println("<- " + headerType(head) + " " + + head); + } + + from = (OtpErlangPid) head.elementAt(1); + to = (OtpErlangPid) head.elementAt(2); + + deliver(new OtpMsg(tag, from, to)); + break; + + // absolutely no idea what to do with these, so we ignore + // them... + case groupLeaderTag: // { GROUPLEADER, FromPid, ToPid} + case nodeLinkTag: // { NODELINK } + // (just show trace) + if (traceLevel >= ctrlThreshold) { + System.out.println("<- " + headerType(head) + " " + + head); + } + break; + + default: + // garbage? + break receive_loop; + } + } // end receive_loop + + // this section reachable only with break + // we have received garbage from peer + deliver(new OtpErlangExit("Remote is sending garbage")); + + } // try + + catch (final OtpAuthException e) { + deliver(e); + } catch (final OtpErlangDecodeException e) { + deliver(new OtpErlangExit("Remote is sending garbage")); + } catch (final IOException e) { + deliver(new OtpErlangExit("Remote has closed connection")); + } finally { + close(); + } + } + + /** + * <p> + * Set the trace level for this connection. Normally tracing is off by + * default unless System property OtpConnection.trace was set. + * </p> + * + * <p> + * The following levels are valid: 0 turns off tracing completely, 1 shows + * ordinary send and receive messages, 2 shows control messages such as link + * and unlink, 3 shows handshaking at connection setup, and 4 shows + * communication with Epmd. Each level includes the information shown by the + * lower ones. + * </p> + * + * @param level + * the level to set. + * + * @return the previous trace level. + */ + public int setTraceLevel(int level) { + final int oldLevel = traceLevel; + + // pin the value + if (level < 0) { + level = 0; + } else if (level > 4) { + level = 4; + } + + traceLevel = level; + + return oldLevel; + } + + /** + * Get the trace level for this connection. + * + * @return the current trace level. + */ + public int getTraceLevel() { + return traceLevel; + } + + /** + * Close the connection to the remote node. + */ + public void close() { + done = true; + connected = false; + synchronized (this) { + try { + if (socket != null) { + if (traceLevel >= ctrlThreshold) { + System.out.println("-> CLOSE"); + } + socket.close(); + } + } catch (final IOException e) { /* ignore socket close errors */ + } finally { + socket = null; + } + } + } + + @Override + protected void finalize() { + close(); + } + + /** + * Determine if the connection is still alive. Note that this method only + * reports the status of the connection, and that it is possible that there + * are unread messages waiting in the receive queue. + * + * @return true if the connection is alive. + */ + public boolean isConnected() { + return connected; + } + + // used by send and send_reg (message types with payload) + protected synchronized void do_send(final OtpOutputStream header, + final OtpOutputStream payload) throws IOException { + try { + if (traceLevel >= sendThreshold) { + // Need to decode header and output buffer to show trace + // message! + // First make OtpInputStream, then decode. + try { + final OtpErlangObject h = header.getOtpInputStream(5) + .read_any(); + System.out.println("-> " + headerType(h) + " " + h); + + OtpErlangObject o = payload.getOtpInputStream(0).read_any(); + System.out.println(" " + o); + o = null; + } catch (final OtpErlangDecodeException e) { + System.out.println(" " + "can't decode output buffer:" + + e); + } + } + + header.writeTo(socket.getOutputStream()); + payload.writeTo(socket.getOutputStream()); + } catch (final IOException e) { + close(); + throw e; + } + } + + // used by the other message types + protected synchronized void do_send(final OtpOutputStream header) + throws IOException { + try { + if (traceLevel >= ctrlThreshold) { + try { + final OtpErlangObject h = header.getOtpInputStream(5) + .read_any(); + System.out.println("-> " + headerType(h) + " " + h); + } catch (final OtpErlangDecodeException e) { + System.out.println(" " + "can't decode output buffer: " + + e); + } + } + header.writeTo(socket.getOutputStream()); + } catch (final IOException e) { + close(); + throw e; + } + } + + protected String headerType(final OtpErlangObject h) { + int tag = -1; + + if (h instanceof OtpErlangTuple) { + tag = (int) ((OtpErlangLong) ((OtpErlangTuple) h).elementAt(0)) + .longValue(); + } + + switch (tag) { + case linkTag: + return "LINK"; + + case sendTag: + return "SEND"; + + case exitTag: + return "EXIT"; + + case unlinkTag: + return "UNLINK"; + + case nodeLinkTag: + return "NODELINK"; + + case regSendTag: + return "REG_SEND"; + + case groupLeaderTag: + return "GROUP_LEADER"; + + case exit2Tag: + return "EXIT2"; + + case sendTTTag: + return "SEND_TT"; + + case exitTTTag: + return "EXIT_TT"; + + case regSendTTTag: + return "REG_SEND_TT"; + + case exit2TTTag: + return "EXIT2_TT"; + } + + return "(unknown type)"; + } + + /* this method now throws exception if we don't get full read */ + protected int readSock(final Socket s, final byte[] b) throws IOException { + int got = 0; + final int len = b.length; + int i; + InputStream is = null; + + synchronized (this) { + if (s == null) { + throw new IOException("expected " + len + + " bytes, socket was closed"); + } + is = s.getInputStream(); + } + + while (got < len) { + i = is.read(b, got, len - got); + + if (i < 0) { + throw new IOException("expected " + len + + " bytes, got EOF after " + got + " bytes"); + } else if (i == 0 && len != 0) { + /* + * This is a corner case. According to + * http://java.sun.com/j2se/1.4.2/docs/api/ class InputStream + * is.read(,,l) can only return 0 if l==0. In other words it + * should not happen, but apparently did. + */ + throw new IOException("Remote connection closed"); + } else { + got += i; + } + } + return got; + } + + protected void doAccept() throws IOException, OtpAuthException { + try { + sendStatus("ok"); + final int our_challenge = genChallenge(); + sendChallenge(peer.distChoose, self.flags, our_challenge); + final int her_challenge = recvChallengeReply(our_challenge); + final byte[] our_digest = genDigest(her_challenge, self.cookie()); + sendChallengeAck(our_digest); + connected = true; + cookieOk = true; + sendCookie = false; + } catch (final IOException ie) { + close(); + throw ie; + } catch (final OtpAuthException ae) { + close(); + throw ae; + } catch (final Exception e) { + final String nn = peer.node(); + close(); + throw new IOException("Error accepting connection from " + nn); + } + if (traceLevel >= handshakeThreshold) { + System.out.println("<- MD5 ACCEPTED " + peer.host()); + } + } + + protected void doConnect(final int port) throws IOException, + OtpAuthException { + try { + socket = new Socket(peer.host(), port); + socket.setTcpNoDelay(true); + + if (traceLevel >= handshakeThreshold) { + System.out.println("-> MD5 CONNECT TO " + peer.host() + ":" + + port); + } + sendName(peer.distChoose, self.flags); + recvStatus(); + final int her_challenge = recvChallenge(); + final byte[] our_digest = genDigest(her_challenge, self.cookie()); + final int our_challenge = genChallenge(); + sendChallengeReply(our_challenge, our_digest); + recvChallengeAck(our_challenge); + cookieOk = true; + sendCookie = false; + } catch (final OtpAuthException ae) { + close(); + throw ae; + } catch (final Exception e) { + close(); + throw new IOException("Cannot connect to peer node"); + } + } + + // This is nooo good as a challenge, + // XXX fix me. + static protected int genChallenge() { + return random.nextInt(); + } + + // Used to debug print a message digest + static String hex0(final byte x) { + final char tab[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f' }; + int uint; + if (x < 0) { + uint = x & 0x7F; + uint |= 1 << 7; + } else { + uint = x; + } + return "" + tab[uint >>> 4] + tab[uint & 0xF]; + } + + static String hex(final byte[] b) { + final StringBuffer sb = new StringBuffer(); + try { + int i; + for (i = 0; i < b.length; ++i) { + sb.append(hex0(b[i])); + } + } catch (final Exception e) { + // Debug function, ignore errors. + } + return sb.toString(); + + } + + protected byte[] genDigest(final int challenge, final String cookie) { + int i; + long ch2; + + if (challenge < 0) { + ch2 = 1L << 31; + ch2 |= challenge & 0x7FFFFFFF; + } else { + ch2 = challenge; + } + final OtpMD5 context = new OtpMD5(); + context.update(cookie); + context.update("" + ch2); + + final int[] tmp = context.final_bytes(); + final byte[] res = new byte[tmp.length]; + for (i = 0; i < tmp.length; ++i) { + res[i] = (byte) (tmp[i] & 0xFF); + } + return res; + } + + protected void sendName(final int dist, final int flags) throws IOException { + + final OtpOutputStream obuf = new OtpOutputStream(); + final String str = self.node(); + obuf.write2BE(str.length() + 7); // 7 bytes + nodename + obuf.write1(AbstractNode.NTYPE_R6); + obuf.write2BE(dist); + obuf.write4BE(flags); + obuf.write(str.getBytes()); + + obuf.writeTo(socket.getOutputStream()); + + if (traceLevel >= handshakeThreshold) { + System.out.println("-> " + "HANDSHAKE sendName" + " flags=" + flags + + " dist=" + dist + " local=" + self); + } + } + + protected void sendChallenge(final int dist, final int flags, + final int challenge) throws IOException { + + final OtpOutputStream obuf = new OtpOutputStream(); + final String str = self.node(); + obuf.write2BE(str.length() + 11); // 11 bytes + nodename + obuf.write1(AbstractNode.NTYPE_R6); + obuf.write2BE(dist); + obuf.write4BE(flags); + obuf.write4BE(challenge); + obuf.write(str.getBytes()); + + obuf.writeTo(socket.getOutputStream()); + + if (traceLevel >= handshakeThreshold) { + System.out.println("-> " + "HANDSHAKE sendChallenge" + " flags=" + + flags + " dist=" + dist + " challenge=" + challenge + + " local=" + self); + } + } + + protected byte[] read2BytePackage() throws IOException, + OtpErlangDecodeException { + + final byte[] lbuf = new byte[2]; + byte[] tmpbuf; + + readSock(socket, lbuf); + final OtpInputStream ibuf = new OtpInputStream(lbuf, 0); + final int len = ibuf.read2BE(); + tmpbuf = new byte[len]; + readSock(socket, tmpbuf); + return tmpbuf; + } + + protected void recvName(final OtpPeer peer) throws IOException { + + String hisname = ""; + + try { + final byte[] tmpbuf = read2BytePackage(); + final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0); + byte[] tmpname; + final int len = tmpbuf.length; + peer.ntype = ibuf.read1(); + if (peer.ntype != AbstractNode.NTYPE_R6) { + throw new IOException("Unknown remote node type"); + } + peer.distLow = peer.distHigh = ibuf.read2BE(); + if (peer.distLow < 5) { + throw new IOException("Unknown remote node type"); + } + peer.flags = ibuf.read4BE(); + tmpname = new byte[len - 7]; + ibuf.readN(tmpname); + hisname = OtpErlangString.newString(tmpname); + // Set the old nodetype parameter to indicate hidden/normal status + // When the old handshake is removed, the ntype should also be. + if ((peer.flags & AbstractNode.dFlagPublished) != 0) { + peer.ntype = AbstractNode.NTYPE_R4_ERLANG; + } else { + peer.ntype = AbstractNode.NTYPE_R4_HIDDEN; + } + + if ((peer.flags & AbstractNode.dFlagExtendedReferences) == 0) { + throw new IOException( + "Handshake failed - peer cannot handle extended references"); + } + + if ((peer.flags & AbstractNode.dFlagExtendedPidsPorts) == 0) { + throw new IOException( + "Handshake failed - peer cannot handle extended pids and ports"); + } + + } catch (final OtpErlangDecodeException e) { + throw new IOException("Handshake failed - not enough data"); + } + + final int i = hisname.indexOf('@', 0); + peer.node = hisname; + peer.alive = hisname.substring(0, i); + peer.host = hisname.substring(i + 1, hisname.length()); + + if (traceLevel >= handshakeThreshold) { + System.out.println("<- " + "HANDSHAKE" + " ntype=" + peer.ntype + + " dist=" + peer.distHigh + " remote=" + peer); + } + } + + protected int recvChallenge() throws IOException { + + int challenge; + + try { + final byte[] buf = read2BytePackage(); + final OtpInputStream ibuf = new OtpInputStream(buf, 0); + peer.ntype = ibuf.read1(); + if (peer.ntype != AbstractNode.NTYPE_R6) { + throw new IOException("Unexpected peer type"); + } + peer.distLow = peer.distHigh = ibuf.read2BE(); + peer.flags = ibuf.read4BE(); + challenge = ibuf.read4BE(); + final byte[] tmpname = new byte[buf.length - 11]; + ibuf.readN(tmpname); + final String hisname = OtpErlangString.newString(tmpname); + if (!hisname.equals(peer.node)) { + throw new IOException( + "Handshake failed - peer has wrong name: " + hisname); + } + + if ((peer.flags & AbstractNode.dFlagExtendedReferences) == 0) { + throw new IOException( + "Handshake failed - peer cannot handle extended references"); + } + + if ((peer.flags & AbstractNode.dFlagExtendedPidsPorts) == 0) { + throw new IOException( + "Handshake failed - peer cannot handle extended pids and ports"); + } + + } catch (final OtpErlangDecodeException e) { + throw new IOException("Handshake failed - not enough data"); + } + + if (traceLevel >= handshakeThreshold) { + System.out.println("<- " + "HANDSHAKE recvChallenge" + " from=" + + peer.node + " challenge=" + challenge + " local=" + self); + } + + return challenge; + } + + protected void sendChallengeReply(final int challenge, final byte[] digest) + throws IOException { + + final OtpOutputStream obuf = new OtpOutputStream(); + obuf.write2BE(21); + obuf.write1(ChallengeReply); + obuf.write4BE(challenge); + obuf.write(digest); + obuf.writeTo(socket.getOutputStream()); + + if (traceLevel >= handshakeThreshold) { + System.out.println("-> " + "HANDSHAKE sendChallengeReply" + + " challenge=" + challenge + " digest=" + hex(digest) + + " local=" + self); + } + } + + // Would use Array.equals in newer JDK... + private boolean digests_equals(final byte[] a, final byte[] b) { + int i; + for (i = 0; i < 16; ++i) { + if (a[i] != b[i]) { + return false; + } + } + return true; + } + + protected int recvChallengeReply(final int our_challenge) + throws IOException, OtpAuthException { + + int challenge; + final byte[] her_digest = new byte[16]; + + try { + final byte[] buf = read2BytePackage(); + final OtpInputStream ibuf = new OtpInputStream(buf, 0); + final int tag = ibuf.read1(); + if (tag != ChallengeReply) { + throw new IOException("Handshake protocol error"); + } + challenge = ibuf.read4BE(); + ibuf.readN(her_digest); + final byte[] our_digest = genDigest(our_challenge, self.cookie()); + if (!digests_equals(her_digest, our_digest)) { + throw new OtpAuthException("Peer authentication error."); + } + } catch (final OtpErlangDecodeException e) { + throw new IOException("Handshake failed - not enough data"); + } + + if (traceLevel >= handshakeThreshold) { + System.out.println("<- " + "HANDSHAKE recvChallengeReply" + + " from=" + peer.node + " challenge=" + challenge + + " digest=" + hex(her_digest) + " local=" + self); + } + + return challenge; + } + + protected void sendChallengeAck(final byte[] digest) throws IOException { + + final OtpOutputStream obuf = new OtpOutputStream(); + obuf.write2BE(17); + obuf.write1(ChallengeAck); + obuf.write(digest); + + obuf.writeTo(socket.getOutputStream()); + + if (traceLevel >= handshakeThreshold) { + System.out.println("-> " + "HANDSHAKE sendChallengeAck" + + " digest=" + hex(digest) + " local=" + self); + } + } + + protected void recvChallengeAck(final int our_challenge) + throws IOException, OtpAuthException { + + final byte[] her_digest = new byte[16]; + try { + final byte[] buf = read2BytePackage(); + final OtpInputStream ibuf = new OtpInputStream(buf, 0); + final int tag = ibuf.read1(); + if (tag != ChallengeAck) { + throw new IOException("Handshake protocol error"); + } + ibuf.readN(her_digest); + final byte[] our_digest = genDigest(our_challenge, self.cookie()); + if (!digests_equals(her_digest, our_digest)) { + throw new OtpAuthException("Peer authentication error."); + } + } catch (final OtpErlangDecodeException e) { + throw new IOException("Handshake failed - not enough data"); + } catch (final Exception e) { + throw new OtpAuthException("Peer authentication error."); + } + + if (traceLevel >= handshakeThreshold) { + System.out.println("<- " + "HANDSHAKE recvChallengeAck" + " from=" + + peer.node + " digest=" + hex(her_digest) + " local=" + + self); + } + } + + protected void sendStatus(final String status) throws IOException { + + final OtpOutputStream obuf = new OtpOutputStream(); + obuf.write2BE(status.length() + 1); + obuf.write1(ChallengeStatus); + obuf.write(status.getBytes()); + + obuf.writeTo(socket.getOutputStream()); + + if (traceLevel >= handshakeThreshold) { + System.out.println("-> " + "HANDSHAKE sendStatus" + " status=" + + status + " local=" + self); + } + } + + protected void recvStatus() throws IOException { + + try { + final byte[] buf = read2BytePackage(); + final OtpInputStream ibuf = new OtpInputStream(buf, 0); + final int tag = ibuf.read1(); + if (tag != ChallengeStatus) { + throw new IOException("Handshake protocol error"); + } + final byte[] tmpbuf = new byte[buf.length - 1]; + ibuf.readN(tmpbuf); + final String status = OtpErlangString.newString(tmpbuf); + + if (status.compareTo("ok") != 0) { + throw new IOException("Peer replied with status '" + status + + "' instead of 'ok'"); + } + } catch (final OtpErlangDecodeException e) { + throw new IOException("Handshake failed - not enough data"); + } + if (traceLevel >= handshakeThreshold) { + System.out.println("<- " + "HANDSHAKE recvStatus (ok)" + " local=" + + self); + } + } + + public void setFlags(final int flags) { + this.flags = flags; + } + + public int getFlags() { + return flags; + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java new file mode 100644 index 0000000000..16cb544a16 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/AbstractNode.java @@ -0,0 +1,252 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * <p> + * Represents an OTP node. + * </p> + * + * <p> + * About nodenames: Erlang nodenames consist of two components, an alivename and + * a hostname separated by '@'. Additionally, there are two nodename formats: + * short and long. Short names are of the form "alive@hostname", while long + * names are of the form "[email protected]". Erlang has + * special requirements regarding the use of the short and long formats, in + * particular they cannot be mixed freely in a network of communicating nodes, + * however Jinterface makes no distinction. See the Erlang documentation for + * more information about nodenames. + * </p> + * + * <p> + * The constructors for the AbstractNode classes will create names exactly as + * you provide them as long as the name contains '@'. If the string you provide + * contains no '@', it will be treated as an alivename and the name of the local + * host will be appended, resulting in a shortname. Nodenames longer than 255 + * characters will be truncated without warning. + * </p> + * + * <p> + * Upon initialization, this class attempts to read the file .erlang.cookie in + * the user's home directory, and uses the trimmed first line of the file as the + * default cookie by those constructors lacking a cookie argument. If for any + * reason the file cannot be found or read, the default cookie will be set to + * the empty string (""). The location of a user's home directory is determined + * using the system property "user.home", which may not be automatically set on + * all platforms. + * </p> + * + * <p> + * Instances of this class cannot be created directly, use one of the subclasses + * instead. + * </p> + */ +public class AbstractNode { + static String localHost = null; + String node; + String host; + String alive; + String cookie; + static String defaultCookie = null; + + // Node types + static final int NTYPE_R6 = 110; // 'n' post-r5, all nodes + static final int NTYPE_R4_ERLANG = 109; // 'm' Only for source compatibility + static final int NTYPE_R4_HIDDEN = 104; // 'h' Only for source compatibility + + // Node capability flags + static final int dFlagPublished = 1; + static final int dFlagAtomCache = 2; + static final int dFlagExtendedReferences = 4; + static final int dFlagDistMonitor = 8; + static final int dFlagFunTags = 0x10; + static final int dFlagDistMonitorName = 0x20; // NOT USED + static final int dFlagHiddenAtomCache = 0x40; // NOT SUPPORTED + static final int dflagNewFunTags = 0x80; + static final int dFlagExtendedPidsPorts = 0x100; + static final int dFlagExportPtrTag = 0x200; // NOT SUPPORTED + static final int dFlagBitBinaries = 0x400; + static final int dFlagNewFloats = 0x800; + + int ntype = NTYPE_R6; + int proto = 0; // tcp/ip + int distHigh = 5; // Cannot talk to nodes before R6 + int distLow = 5; // Cannot talk to nodes before R6 + int creation = 0; + int flags = dFlagExtendedReferences | dFlagExtendedPidsPorts + | dFlagBitBinaries | dFlagNewFloats | dFlagFunTags + | dflagNewFunTags; + + /* initialize hostname and default cookie */ + static { + try { + localHost = InetAddress.getLocalHost().getHostName(); + /* + * Make sure it's a short name, i.e. strip of everything after first + * '.' + */ + final int dot = localHost.indexOf("."); + if (dot != -1) { + localHost = localHost.substring(0, dot); + } + } catch (final UnknownHostException e) { + localHost = "localhost"; + } + + final String dotCookieFilename = System.getProperty("user.home") + + File.separator + ".erlang.cookie"; + BufferedReader br = null; + + try { + final File dotCookieFile = new File(dotCookieFilename); + + br = new BufferedReader(new FileReader(dotCookieFile)); + defaultCookie = br.readLine().trim(); + } catch (final IOException e) { + defaultCookie = ""; + } finally { + try { + if (br != null) { + br.close(); + } + } catch (final IOException e) { + } + } + } + + protected AbstractNode() { + } + + /** + * Create a node with the given name and the default cookie. + */ + protected AbstractNode(final String node) { + this(node, defaultCookie); + } + + /** + * Create a node with the given name and cookie. + */ + protected AbstractNode(final String name, final String cookie) { + this.cookie = cookie; + + final int i = name.indexOf('@', 0); + if (i < 0) { + alive = name; + host = localHost; + } else { + alive = name.substring(0, i); + host = name.substring(i + 1, name.length()); + } + + if (alive.length() > 0xff) { + alive = alive.substring(0, 0xff); + } + + node = alive + "@" + host; + } + + /** + * Get the name of this node. + * + * @return the name of the node represented by this object. + */ + public String node() { + return node; + } + + /** + * Get the hostname part of the nodename. Nodenames are composed of two + * parts, an alivename and a hostname, separated by '@'. This method returns + * the part of the nodename following the '@'. + * + * @return the hostname component of the nodename. + */ + public String host() { + return host; + } + + /** + * Get the alivename part of the hostname. Nodenames are composed of two + * parts, an alivename and a hostname, separated by '@'. This method returns + * the part of the nodename preceding the '@'. + * + * @return the alivename component of the nodename. + */ + public String alive() { + return alive; + } + + /** + * Get the authorization cookie used by this node. + * + * @return the authorization cookie used by this node. + */ + public String cookie() { + return cookie; + } + + // package scope + int type() { + return ntype; + } + + // package scope + int distHigh() { + return distHigh; + } + + // package scope + int distLow() { + return distLow; + } + + // package scope: useless information? + int proto() { + return proto; + } + + // package scope + int creation() { + return creation; + } + + /** + * Set the authorization cookie used by this node. + * + * @return the previous authorization cookie used by this node. + */ + public String setCookie(final String cookie) { + final String prev = this.cookie; + this.cookie = cookie; + return prev; + } + + @Override + public String toString() { + return node(); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/GenericQueue.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/GenericQueue.java new file mode 100644 index 0000000000..80bb02f16c --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/GenericQueue.java @@ -0,0 +1,186 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +/** + * This class implements a generic FIFO queue. There is no upper bound on the + * length of the queue, items are linked. + */ + +public class GenericQueue { + private static final int open = 0; + private static final int closing = 1; + private static final int closed = 2; + + private int status; + private Bucket head; + private Bucket tail; + private int count; + + private void init() { + head = null; + tail = null; + count = 0; + } + + /** Create an empty queue */ + public GenericQueue() { + init(); + status = open; + } + + /** Clear a queue */ + public void flush() { + init(); + } + + public void close() { + status = closing; + } + + /** + * Add an object to the tail of the queue. + * + * @param o + * Object to insert in the queue + */ + public synchronized void put(final Object o) { + final Bucket b = new Bucket(o); + + if (tail != null) { + tail.setNext(b); + tail = b; + } else { + // queue was empty but has one element now + head = tail = b; + } + count++; + + // notify any waiting tasks + notify(); + } + + /** + * Retrieve an object from the head of the queue, or block until one + * arrives. + * + * @return The object at the head of the queue. + */ + public synchronized Object get() { + Object o = null; + + while ((o = tryGet()) == null) { + try { + this.wait(); + } catch (final InterruptedException e) { + } + } + return o; + } + + /** + * Retrieve an object from the head of the queue, blocking until one arrives + * or until timeout occurs. + * + * @param timeout + * Maximum time to block on queue, in ms. Use 0 to poll the + * queue. + * + * @exception InterruptedException + * if the operation times out. + * + * @return The object at the head of the queue, or null if none arrived in + * time. + */ + public synchronized Object get(final long timeout) + throws InterruptedException { + if (status == closed) { + return null; + } + + long currentTime = System.currentTimeMillis(); + final long stopTime = currentTime + timeout; + Object o = null; + + while (true) { + if ((o = tryGet()) != null) { + return o; + } + + currentTime = System.currentTimeMillis(); + if (stopTime <= currentTime) { + throw new InterruptedException("Get operation timed out"); + } + + try { + this.wait(stopTime - currentTime); + } catch (final InterruptedException e) { + // ignore, but really should retry operation instead + } + } + } + + // attempt to retrieve message from queue head + public Object tryGet() { + Object o = null; + + if (head != null) { + o = head.getContents(); + head = head.getNext(); + count--; + + if (head == null) { + tail = null; + count = 0; + } + } + + return o; + } + + public synchronized int getCount() { + return count; + } + + /* + * The Bucket class. The queue is implemented as a linked list of Buckets. + * The container holds the queued object and a reference to the next Bucket. + */ + class Bucket { + private Bucket next; + private final Object contents; + + public Bucket(final Object o) { + next = null; + contents = o; + } + + public void setNext(final Bucket newNext) { + next = newNext; + } + + public Bucket getNext() { + return next; + } + + public Object getContents() { + return contents; + } + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/Link.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/Link.java new file mode 100644 index 0000000000..2b085761e3 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/Link.java @@ -0,0 +1,57 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +// package scope +class Link { + private final OtpErlangPid local; + private final OtpErlangPid remote; + private int hashCodeValue = 0; + + public Link(final OtpErlangPid local, final OtpErlangPid remote) { + this.local = local; + this.remote = remote; + } + + public OtpErlangPid local() { + return local; + } + + public OtpErlangPid remote() { + return remote; + } + + public boolean contains(final OtpErlangPid pid) { + return local.equals(pid) || remote.equals(pid); + } + + public boolean equals(final OtpErlangPid local, final OtpErlangPid remote) { + return this.local.equals(local) && this.remote.equals(remote) + || this.local.equals(remote) && this.remote.equals(local); + } + + public int hashCode() { + if (hashCodeValue == 0) { + OtpErlangObject.Hash hash = new OtpErlangObject.Hash(5); + hash.combine(local.hashCode() + remote.hashCode()); + hashCodeValue = hash.valueOf(); + } + return hashCodeValue; + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/Links.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/Links.java new file mode 100644 index 0000000000..0bb4a708a3 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/Links.java @@ -0,0 +1,123 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +// package scope +class Links { + Link[] links; + int count; + + Links() { + this(10); + } + + Links(final int initialSize) { + links = new Link[initialSize]; + count = 0; + } + + synchronized void addLink(final OtpErlangPid local, + final OtpErlangPid remote) { + if (find(local, remote) == -1) { + if (count >= links.length) { + final Link[] tmp = new Link[count * 2]; + System.arraycopy(links, 0, tmp, 0, count); + links = tmp; + } + links[count++] = new Link(local, remote); + } + } + + synchronized void removeLink(final OtpErlangPid local, + final OtpErlangPid remote) { + int i; + + if ((i = find(local, remote)) != -1) { + count--; + links[i] = links[count]; + links[count] = null; + } + } + + synchronized boolean exists(final OtpErlangPid local, + final OtpErlangPid remote) { + return find(local, remote) != -1; + } + + synchronized int find(final OtpErlangPid local, final OtpErlangPid remote) { + for (int i = 0; i < count; i++) { + if (links[i].equals(local, remote)) { + return i; + } + } + return -1; + } + + int count() { + return count; + } + + /* all local pids get notified about broken connection */ + synchronized OtpErlangPid[] localPids() { + OtpErlangPid[] ret = null; + if (count != 0) { + ret = new OtpErlangPid[count]; + for (int i = 0; i < count; i++) { + ret[i] = links[i].local(); + } + } + return ret; + } + + /* all remote pids get notified about failed pid */ + synchronized OtpErlangPid[] remotePids() { + OtpErlangPid[] ret = null; + if (count != 0) { + ret = new OtpErlangPid[count]; + for (int i = 0; i < count; i++) { + ret[i] = links[i].remote(); + } + } + return ret; + } + + /* clears the link table, returns a copy */ + synchronized Link[] clearLinks() { + Link[] ret = null; + if (count != 0) { + ret = new Link[count]; + for (int i = 0; i < count; i++) { + ret[i] = links[i]; + links[i] = null; + } + count = 0; + } + return ret; + } + + /* returns a copy of the link table */ + synchronized Link[] links() { + Link[] ret = null; + if (count != 0) { + ret = new Link[count]; + System.arraycopy(links, 0, ret, 0, count); + } + return ret; + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/Makefile.otp b/lib/jinterface/java_src/com/ericsson/otp/erlang/Makefile.otp new file mode 100644 index 0000000000..d0ff9cda34 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/Makefile.otp @@ -0,0 +1,113 @@ +# -*-Makefile-*- + +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2000-2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# +include $(ERL_TOP)/make/target.mk + +JAVA_DEST_ROOT = $(ERL_TOP)/lib/jinterface/priv/ +JAVA_SRC_ROOT = $(ERL_TOP)/lib/jinterface/java_src/ +JAVA_CLASS_SUBDIR = com/ericsson/otp/erlang/ + +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include $(ERL_TOP)/lib/jinterface/vsn.mk +VSN=$(JINTERFACE_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/jinterface-$(VSN) + +# +# JAVA macros +# + +# don't add filenames to the Makefile! +# all java sourcefiles listed in common include file +include $(ERL_TOP)/lib/jinterface/java_src/$(JAVA_CLASS_SUBDIR)/java_files + +TARGET_FILES= $(JAVA_FILES:%=$(JAVA_DEST_ROOT)$(JAVA_CLASS_SUBDIR)%.class) +JAVA_SRC= $(JAVA_FILES:%=%.java) + +JARFILE= OtpErlang.jar + + +# ---------------------------------------------------- +# Programs and Flags +# ---------------------------------------------------- + +JAR= jar + +CLASSPATH = $(JAVA_SRC_ROOT) + +JAVADOCFLAGS=-d $(DOCDIR) +JAVAFLAGS=-d $(JAVA_DEST_ROOT) +JARFLAGS=-cvf + +JAVA_OPTIONS = + +ifeq ($(TESTROOT),) +RELEASE_PATH=$(ERL_TOP)/release/$(TARGET) +else +RELEASE_PATH=$(TESTROOT) +endif + + +# ---------------------------------------------------- +# Make Rules +# ---------------------------------------------------- + +debug opt: make_dirs $(JAVA_DEST_ROOT)$(JARFILE) + +make_dirs: + if [ ! -d "$(JAVA_DEST_ROOT)" ];then mkdir "$(JAVA_DEST_ROOT)"; fi + +$(JAVA_DEST_ROOT)$(JARFILE): $(TARGET_FILES) + @(cd $(JAVA_DEST_ROOT) ; $(JAR) $(JARFLAGS) $(JARFILE) $(JAVA_CLASS_SUBDIR)) + +clean: + rm -f $(TARGET_FILES) *~ + +docs: + +# ---------------------------------------------------- +# Release Targets +# ---------------------------------------------------- + +# include $(ERL_TOP)/make/otp_release_targets.mk + +release release_docs release_tests release_html: + $(MAKE) -f Makefile.otp $(MFLAGS) RELEASE_PATH=$(RELEASE_PATH) $(TARGET_MAKEFILE) $@_spec + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/java_src/com/ericsson/otp/erlang + $(INSTALL_DATA) $(JAVA_SRC) $(RELSYSDIR)/java_src/com/ericsson/otp/erlang + $(INSTALL_DIR) $(RELSYSDIR)/priv + $(INSTALL_DATA) $(JAVA_DEST_ROOT)$(JARFILE) $(RELSYSDIR)/priv + +release_docs_spec: + + + + +# ---------------------------------------------------- + diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpAuthException.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpAuthException.java new file mode 100644 index 0000000000..39d254d9fa --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpAuthException.java @@ -0,0 +1,37 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +/** + * Exception raised when a node attempts to establish a communication channel + * when it is not authorized to do so, or when a node sends a message containing + * an invalid cookie on an established channel. + * + * @see OtpConnection + */ +public class OtpAuthException extends OtpException { + private static final long serialVersionUID = 1L; + + /** + * Provides a detailed message. + */ + public OtpAuthException(final String s) { + super(s); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpConnection.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpConnection.java new file mode 100644 index 0000000000..8e8bd473c8 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpConnection.java @@ -0,0 +1,584 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.IOException; +import java.net.Socket; + +/** + * Maintains a connection between a Java process and a remote Erlang, Java or C + * node. The object maintains connection state and allows data to be sent to and + * received from the peer. + * + * <p> + * Once a connection is established between the local node and a remote node, + * the connection object can be used to send and receive messages between the + * nodes and make rpc calls (assuming that the remote node is a real Erlang + * node). + * + * <p> + * The various receive methods are all blocking and will return only when a + * valid message has been received or an exception is raised. + * + * <p> + * If an exception occurs in any of the methods in this class, the connection + * will be closed and must be explicitely reopened in order to resume + * communication with the peer. + * + * <p> + * It is not possible to create an instance of this class directly. + * OtpConnection objects are returned by {@link OtpSelf#connect(OtpPeer) + * OtpSelf.connect()} and {@link OtpSelf#accept() OtpSelf.accept()}. + */ +public class OtpConnection extends AbstractConnection { + protected OtpSelf self; + protected GenericQueue queue; // messages get delivered here + + /* + * Accept an incoming connection from a remote node. Used by {@link + * OtpSelf#accept() OtpSelf.accept()} to create a connection based on data + * received when handshaking with the peer node, when the remote node is the + * connection intitiator. + * + * @exception java.io.IOException if it was not possible to connect to the + * peer. + * + * @exception OtpAuthException if handshake resulted in an authentication + * error + */ + // package scope + OtpConnection(final OtpSelf self, final Socket s) throws IOException, + OtpAuthException { + super(self, s); + this.self = self; + queue = new GenericQueue(); + start(); + } + + /* + * Intiate and open a connection to a remote node. + * + * @exception java.io.IOException if it was not possible to connect to the + * peer. + * + * @exception OtpAuthException if handshake resulted in an authentication + * error. + */ + // package scope + OtpConnection(final OtpSelf self, final OtpPeer other) throws IOException, + OtpAuthException { + super(self, other); + this.self = self; + queue = new GenericQueue(); + start(); + } + + @Override + public void deliver(final Exception e) { + queue.put(e); + } + + @Override + public void deliver(final OtpMsg msg) { + queue.put(msg); + } + + /** + * Get information about the node at the peer end of this connection. + * + * @return the {@link OtpPeer Node} representing the peer node. + */ + public OtpPeer peer() { + return peer; + } + + /** + * Get information about the node at the local end of this connection. + * + * @return the {@link OtpSelf Node} representing the local node. + */ + public OtpSelf self() { + return self; + } + + /** + * Return the number of messages currently waiting in the receive queue for + * this connection. + */ + public int msgCount() { + return queue.getCount(); + } + + /** + * Receive a message from a remote process. This method blocks until a valid + * message is received or an exception is raised. + * + * <p> + * If the remote node sends a message that cannot be decoded properly, the + * connection is closed and the method throws an exception. + * + * @return an object containing a single Erlang term. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + * + * @exception OtpErlangExit + * if an exit signal is received from a process on the + * peer node. + * + * @exception OtpAuthException + * if the remote node sends a message containing an + * invalid cookie. + */ + public OtpErlangObject receive() throws IOException, OtpErlangExit, + OtpAuthException { + try { + return receiveMsg().getMsg(); + } catch (final OtpErlangDecodeException e) { + close(); + throw new IOException(e.getMessage()); + } + } + + /** + * Receive a message from a remote process. This method blocks at most for + * the specified time, until a valid message is received or an exception is + * raised. + * + * <p> + * If the remote node sends a message that cannot be decoded properly, the + * connection is closed and the method throws an exception. + * + * @param timeout + * the time in milliseconds that this operation will block. + * Specify 0 to poll the queue. + * + * @return an object containing a single Erlang term. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + * + * @exception OtpErlangExit + * if an exit signal is received from a process on the + * peer node. + * + * @exception OtpAuthException + * if the remote node sends a message containing an + * invalid cookie. + * + * @exception InterruptedException + * if no message if the method times out before a message + * becomes available. + */ + public OtpErlangObject receive(final long timeout) + throws InterruptedException, IOException, OtpErlangExit, + OtpAuthException { + try { + return receiveMsg(timeout).getMsg(); + } catch (final OtpErlangDecodeException e) { + close(); + throw new IOException(e.getMessage()); + } + } + + /** + * Receive a raw (still encoded) message from a remote process. This message + * blocks until a valid message is received or an exception is raised. + * + * <p> + * If the remote node sends a message that cannot be decoded properly, the + * connection is closed and the method throws an exception. + * + * @return an object containing a raw (still encoded) Erlang term. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + * + * @exception OtpErlangExit + * if an exit signal is received from a process on the + * peer node, or if the connection is lost for any + * reason. + * + * @exception OtpAuthException + * if the remote node sends a message containing an + * invalid cookie. + */ + public OtpInputStream receiveBuf() throws IOException, OtpErlangExit, + OtpAuthException { + return receiveMsg().getMsgBuf(); + } + + /** + * Receive a raw (still encoded) message from a remote process. This message + * blocks at most for the specified time until a valid message is received + * or an exception is raised. + * + * <p> + * If the remote node sends a message that cannot be decoded properly, the + * connection is closed and the method throws an exception. + * + * @param timeout + * the time in milliseconds that this operation will block. + * Specify 0 to poll the queue. + * + * @return an object containing a raw (still encoded) Erlang term. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + * + * @exception OtpErlangExit + * if an exit signal is received from a process on the + * peer node, or if the connection is lost for any + * reason. + * + * @exception OtpAuthException + * if the remote node sends a message containing an + * invalid cookie. + * + * @exception InterruptedException + * if no message if the method times out before a message + * becomes available. + */ + public OtpInputStream receiveBuf(final long timeout) + throws InterruptedException, IOException, OtpErlangExit, + OtpAuthException { + return receiveMsg(timeout).getMsgBuf(); + } + + /** + * Receive a messge complete with sender and recipient information. + * + * @return an {@link OtpMsg OtpMsg} containing the header information about + * the sender and recipient, as well as the actual message contents. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + * + * @exception OtpErlangExit + * if an exit signal is received from a process on the + * peer node, or if the connection is lost for any + * reason. + * + * @exception OtpAuthException + * if the remote node sends a message containing an + * invalid cookie. + */ + public OtpMsg receiveMsg() throws IOException, OtpErlangExit, + OtpAuthException { + final Object o = queue.get(); + + if (o instanceof OtpMsg) { + return (OtpMsg) o; + } else if (o instanceof IOException) { + throw (IOException) o; + } else if (o instanceof OtpErlangExit) { + throw (OtpErlangExit) o; + } else if (o instanceof OtpAuthException) { + throw (OtpAuthException) o; + } + + return null; + } + + /** + * Receive a messge complete with sender and recipient information. This + * method blocks at most for the specified time. + * + * @param timeout + * the time in milliseconds that this operation will block. + * Specify 0 to poll the queue. + * + * @return an {@link OtpMsg OtpMsg} containing the header information about + * the sender and recipient, as well as the actual message contents. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + * + * @exception OtpErlangExit + * if an exit signal is received from a process on the + * peer node, or if the connection is lost for any + * reason. + * + * @exception OtpAuthException + * if the remote node sends a message containing an + * invalid cookie. + * + * @exception InterruptedException + * if no message if the method times out before a message + * becomes available. + */ + public OtpMsg receiveMsg(final long timeout) throws InterruptedException, + IOException, OtpErlangExit, OtpAuthException { + final Object o = queue.get(timeout); + + if (o instanceof OtpMsg) { + return (OtpMsg) o; + } else if (o instanceof IOException) { + throw (IOException) o; + } else if (o instanceof OtpErlangExit) { + throw (OtpErlangExit) o; + } else if (o instanceof OtpAuthException) { + throw (OtpAuthException) o; + } + + return null; + } + + /** + * Send a message to a process on a remote node. + * + * @param dest + * the Erlang PID of the remote process. + * @param msg + * the message to send. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + */ + public void send(final OtpErlangPid dest, final OtpErlangObject msg) + throws IOException { + // encode and send the message + super.sendBuf(self.pid(), dest, new OtpOutputStream(msg)); + } + + /** + * Send a message to a named process on a remote node. + * + * @param dest + * the name of the remote process. + * @param msg + * the message to send. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + */ + public void send(final String dest, final OtpErlangObject msg) + throws IOException { + // encode and send the message + super.sendBuf(self.pid(), dest, new OtpOutputStream(msg)); + } + + /** + * Send a pre-encoded message to a named process on a remote node. + * + * @param dest + * the name of the remote process. + * @param payload + * the encoded message to send. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + */ + public void sendBuf(final String dest, final OtpOutputStream payload) + throws IOException { + super.sendBuf(self.pid(), dest, payload); + } + + /** + * Send a pre-encoded message to a process on a remote node. + * + * @param dest + * the Erlang PID of the remote process. + * @param msg + * the encoded message to send. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + */ + public void sendBuf(final OtpErlangPid dest, final OtpOutputStream payload) + throws IOException { + super.sendBuf(self.pid(), dest, payload); + } + + /** + * Send an RPC request to the remote Erlang node. This convenience function + * creates the following message and sends it to 'rex' on the remote node: + * + * <pre> + * { self, { call, Mod, Fun, Args, user } } + * </pre> + * + * <p> + * Note that this method has unpredicatble results if the remote node is not + * an Erlang node. + * </p> + * + * @param mod + * the name of the Erlang module containing the function to + * be called. + * @param fun + * the name of the function to call. + * @param args + * an array of Erlang terms, to be used as arguments to the + * function. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + */ + public void sendRPC(final String mod, final String fun, + final OtpErlangObject[] args) throws IOException { + sendRPC(mod, fun, new OtpErlangList(args)); + } + + /** + * Send an RPC request to the remote Erlang node. This convenience function + * creates the following message and sends it to 'rex' on the remote node: + * + * <pre> + * { self, { call, Mod, Fun, Args, user } } + * </pre> + * + * <p> + * Note that this method has unpredicatble results if the remote node is not + * an Erlang node. + * </p> + * + * @param mod + * the name of the Erlang module containing the function to + * be called. + * @param fun + * the name of the function to call. + * @param args + * a list of Erlang terms, to be used as arguments to the + * function. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + */ + public void sendRPC(final String mod, final String fun, + final OtpErlangList args) throws IOException { + final OtpErlangObject[] rpc = new OtpErlangObject[2]; + final OtpErlangObject[] call = new OtpErlangObject[5]; + + /* {self, { call, Mod, Fun, Args, user}} */ + + call[0] = new OtpErlangAtom("call"); + call[1] = new OtpErlangAtom(mod); + call[2] = new OtpErlangAtom(fun); + call[3] = args; + call[4] = new OtpErlangAtom("user"); + + rpc[0] = self.pid(); + rpc[1] = new OtpErlangTuple(call); + + send("rex", new OtpErlangTuple(rpc)); + } + + /** + * Receive an RPC reply from the remote Erlang node. This convenience + * function receives a message from the remote node, and expects it to have + * the following format: + * + * <pre> + * { rex, Term } + * </pre> + * + * @return the second element of the tuple if the received message is a + * two-tuple, otherwise null. No further error checking is + * performed. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + * + * @exception OtpErlangExit + * if an exit signal is received from a process on the + * peer node. + * + * @exception OtpAuthException + * if the remote node sends a message containing an + * invalid cookie. + */ + public OtpErlangObject receiveRPC() throws IOException, OtpErlangExit, + OtpAuthException { + + final OtpErlangObject msg = receive(); + + if (msg instanceof OtpErlangTuple) { + final OtpErlangTuple t = (OtpErlangTuple) msg; + if (t.arity() == 2) { + return t.elementAt(1); // obs: second element + } + } + + return null; + } + + /** + * Create a link between the local node and the specified process on the + * remote node. If the link is still active when the remote process + * terminates, an exit signal will be sent to this connection. Use + * {@link #unlink unlink()} to remove the link. + * + * @param dest + * the Erlang PID of the remote process. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + */ + public void link(final OtpErlangPid dest) throws IOException { + super.sendLink(self.pid(), dest); + } + + /** + * Remove a link between the local node and the specified process on the + * remote node. This method deactivates links created with + * {@link #link link()}. + * + * @param dest + * the Erlang PID of the remote process. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + */ + public void unlink(final OtpErlangPid dest) throws IOException { + super.sendUnlink(self.pid(), dest); + } + + /** + * Send an exit signal to a remote process. + * + * @param dest + * the Erlang PID of the remote process. + * @param reason + * an Erlang term describing the exit reason. + * + * @exception java.io.IOException + * if the connection is not active or a communication + * error occurs. + */ + public void exit(final OtpErlangPid dest, final OtpErlangObject reason) + throws IOException { + super.sendExit2(self.pid(), dest, reason); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpCookedConnection.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpCookedConnection.java new file mode 100644 index 0000000000..5abf6e33f7 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpCookedConnection.java @@ -0,0 +1,244 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.IOException; +import java.net.Socket; + +/** + * <p> + * Maintains a connection between a Java process and a remote Erlang, Java or C + * node. The object maintains connection state and allows data to be sent to and + * received from the peer. + * </p> + * + * <p> + * Once a connection is established between the local node and a remote node, + * the connection object can be used to send and receive messages between the + * nodes. + * </p> + * + * <p> + * The various receive methods are all blocking and will return only when a + * valid message has been received or an exception is raised. + * </p> + * + * <p> + * If an exception occurs in any of the methods in this class, the connection + * will be closed and must be reopened in order to resume communication with the + * peer. + * </p> + * + * <p> + * The message delivery methods in this class deliver directly to + * {@link OtpMbox mailboxes} in the {@link OtpNode OtpNode} class. + * </p> + * + * <p> + * It is not possible to create an instance of this class directly. + * OtpCookedConnection objects are created as needed by the underlying mailbox + * mechanism. + * </p> + */ +public class OtpCookedConnection extends AbstractConnection { + protected OtpNode self; + + /* + * The connection needs to know which local pids have links that pass + * through here, so that they can be notified in case of connection failure + */ + protected Links links = null; + + /* + * Accept an incoming connection from a remote node. Used by {@link + * OtpSelf#accept() OtpSelf.accept()} to create a connection based on data + * received when handshaking with the peer node, when the remote node is the + * connection intitiator. + * + * @exception java.io.IOException if it was not possible to connect to the + * peer. + * + * @exception OtpAuthException if handshake resulted in an authentication + * error + */ + // package scope + OtpCookedConnection(final OtpNode self, final Socket s) throws IOException, + OtpAuthException { + super(self, s); + this.self = self; + links = new Links(25); + start(); + } + + /* + * Intiate and open a connection to a remote node. + * + * @exception java.io.IOException if it was not possible to connect to the + * peer. + * + * @exception OtpAuthException if handshake resulted in an authentication + * error. + */ + // package scope + OtpCookedConnection(final OtpNode self, final OtpPeer other) + throws IOException, OtpAuthException { + super(self, other); + this.self = self; + links = new Links(25); + start(); + } + + // pass the error to the node + @Override + public void deliver(final Exception e) { + self.deliverError(this, e); + return; + } + + /* + * pass the message to the node for final delivery. Note that the connection + * itself needs to know about links (in case of connection failure), so we + * snoop for link/unlink too here. + */ + @Override + public void deliver(final OtpMsg msg) { + final boolean delivered = self.deliver(msg); + + switch (msg.type()) { + case OtpMsg.linkTag: + if (delivered) { + links.addLink(msg.getRecipientPid(), msg.getSenderPid()); + } else { + try { + // no such pid - send exit to sender + super.sendExit(msg.getRecipientPid(), msg.getSenderPid(), + new OtpErlangAtom("noproc")); + } catch (final IOException e) { + } + } + break; + + case OtpMsg.unlinkTag: + case OtpMsg.exitTag: + links.removeLink(msg.getRecipientPid(), msg.getSenderPid()); + break; + + case OtpMsg.exit2Tag: + break; + } + + return; + } + + /* + * send to pid + */ + void send(final OtpErlangPid from, final OtpErlangPid dest, + final OtpErlangObject msg) throws IOException { + // encode and send the message + sendBuf(from, dest, new OtpOutputStream(msg)); + } + + /* + * send to remote name dest is recipient's registered name, the nodename is + * implied by the choice of connection. + */ + void send(final OtpErlangPid from, final String dest, + final OtpErlangObject msg) throws IOException { + // encode and send the message + sendBuf(from, dest, new OtpOutputStream(msg)); + } + + @Override + public void close() { + super.close(); + breakLinks(); + } + + @Override + protected void finalize() { + close(); + } + + /* + * this one called by dying/killed process + */ + void exit(final OtpErlangPid from, final OtpErlangPid to, + final OtpErlangObject reason) { + try { + super.sendExit(from, to, reason); + } catch (final Exception e) { + } + } + + /* + * this one called explicitely by user code => use exit2 + */ + void exit2(final OtpErlangPid from, final OtpErlangPid to, + final OtpErlangObject reason) { + try { + super.sendExit2(from, to, reason); + } catch (final Exception e) { + } + } + + /* + * snoop for outgoing links and update own table + */ + synchronized void link(final OtpErlangPid from, final OtpErlangPid to) + throws OtpErlangExit { + try { + super.sendLink(from, to); + links.addLink(from, to); + } catch (final IOException e) { + throw new OtpErlangExit("noproc", to); + } + } + + /* + * snoop for outgoing unlinks and update own table + */ + synchronized void unlink(final OtpErlangPid from, final OtpErlangPid to) { + links.removeLink(from, to); + try { + super.sendUnlink(from, to); + } catch (final IOException e) { + } + } + + /* + * When the connection fails - send exit to all local pids with links + * through this connection + */ + synchronized void breakLinks() { + if (links != null) { + final Link[] l = links.clearLinks(); + + if (l != null) { + final int len = l.length; + + for (int i = 0; i < len; i++) { + // send exit "from" remote pids to local ones + self.deliver(new OtpMsg(OtpMsg.exitTag, l[i].remote(), l[i] + .local(), new OtpErlangAtom("noconnection"))); + } + } + } + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java new file mode 100644 index 0000000000..3bb678c2cc --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpEpmd.java @@ -0,0 +1,569 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; + +/** + * Provides methods for registering, unregistering and looking up nodes with the + * Erlang portmapper daemon (Epmd). For each registered node, Epmd maintains + * information about the port on which incoming connections are accepted, as + * well as which versions of the Erlang communication protocolt the node + * supports. + * + * <p> + * Nodes wishing to contact other nodes must first request information from Epmd + * before a connection can be set up, however this is done automatically by + * {@link OtpSelf#connect(OtpPeer) OtpSelf.connect()} when necessary. + * + * <p> + * The methods {@link #publishPort(OtpLocalNode) publishPort()} and + * {@link #unPublishPort(OtpLocalNode) unPublishPort()} will fail if an Epmd + * process is not running on the localhost. Additionally + * {@link #lookupPort(AbstractNode) lookupPort()} will fail if there is no Epmd + * process running on the host where the specified node is running. See the + * Erlang documentation for information about starting Epmd. + * + * <p> + * This class contains only static methods, there are no constructors. + */ +public class OtpEpmd { + + private static class EpmdPort { + private static int epmdPort = 0; + + public static int get() { + if (epmdPort == 0) { + String env; + try { + env = System.getenv("ERL_EPMD_PORT"); + } + catch (java.lang.SecurityException e) { + env = null; + } + epmdPort = (env != null) ? Integer.parseInt(env) : 4369; + } + return epmdPort; + } + public static void set(int port) { + epmdPort = port; + } + } + // common values + private static final byte stopReq = (byte) 115; + + // version specific value + private static final byte port3req = (byte) 112; + private static final byte publish3req = (byte) 97; + private static final byte publish3ok = (byte) 89; + + private static final byte port4req = (byte) 122; + private static final byte port4resp = (byte) 119; + private static final byte publish4req = (byte) 120; + private static final byte publish4resp = (byte) 121; + private static final byte names4req = (byte) 110; + + private static int traceLevel = 0; + private static final int traceThreshold = 4; + + static { + // debug this connection? + final String trace = System.getProperties().getProperty( + "OtpConnection.trace"); + try { + if (trace != null) { + traceLevel = Integer.valueOf(trace).intValue(); + } + } catch (final NumberFormatException e) { + traceLevel = 0; + } + } + + // only static methods: no public constructors + // hmm, idea: singleton constructor could spawn epmd process + private OtpEpmd() { + } + + + /** + * Set the port number to be used to contact the epmd process. + * Only needed when the default port is not desired and system environment + * variable ERL_EPMD_PORT can not be read (applet). + */ + public static void useEpmdPort(int port) { + EpmdPort.set(port); + } + + /** + * Determine what port a node listens for incoming connections on. + * + * @return the listen port for the specified node, or 0 if the node was not + * registered with Epmd. + * + * @exception java.io.IOException + * if there was no response from the name server. + */ + public static int lookupPort(final AbstractNode node) throws IOException { + try { + return r4_lookupPort(node); + } catch (final IOException e) { + return r3_lookupPort(node); + } + } + + /** + * Register with Epmd, so that other nodes are able to find and connect to + * it. + * + * @param node + * the server node that should be registered with Epmd. + * + * @return true if the operation was successful. False if the node was + * already registered. + * + * @exception java.io.IOException + * if there was no response from the name server. + */ + public static boolean publishPort(final OtpLocalNode node) + throws IOException { + Socket s = null; + + try { + s = r4_publish(node); + } catch (final IOException e) { + s = r3_publish(node); + } + + node.setEpmd(s); + + return s != null; + } + + // Ask epmd to close his end of the connection. + // Caller should close his epmd socket as well. + // This method is pretty forgiving... + /** + * Unregister from Epmd. Other nodes wishing to connect will no longer be + * able to. + * + * <p> + * This method does not report any failures. + */ + public static void unPublishPort(final OtpLocalNode node) { + Socket s = null; + + try { + s = new Socket((String) null, EpmdPort.get()); + final OtpOutputStream obuf = new OtpOutputStream(); + obuf.write2BE(node.alive().length() + 1); + obuf.write1(stopReq); + obuf.writeN(node.alive().getBytes()); + obuf.writeTo(s.getOutputStream()); + // don't even wait for a response (is there one?) + if (traceLevel >= traceThreshold) { + System.out.println("-> UNPUBLISH " + node + " port=" + + node.port()); + System.out.println("<- OK (assumed)"); + } + } catch (final Exception e) {/* ignore all failures */ + } finally { + try { + if (s != null) { + s.close(); + } + } catch (final IOException e) { /* ignore close failure */ + } + s = null; + } + } + + private static int r3_lookupPort(final AbstractNode node) + throws IOException { + int port = 0; + Socket s = null; + + try { + final OtpOutputStream obuf = new OtpOutputStream(); + s = new Socket(node.host(), EpmdPort.get()); + + // build and send epmd request + // length[2], tag[1], alivename[n] (length = n+1) + obuf.write2BE(node.alive().length() + 1); + obuf.write1(port3req); + obuf.writeN(node.alive().getBytes()); + + // send request + obuf.writeTo(s.getOutputStream()); + + if (traceLevel >= traceThreshold) { + System.out.println("-> LOOKUP (r3) " + node); + } + + // receive and decode reply + final byte[] tmpbuf = new byte[100]; + + s.getInputStream().read(tmpbuf); + final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0); + + port = ibuf.read2BE(); + } catch (final IOException e) { + if (traceLevel >= traceThreshold) { + System.out.println("<- (no response)"); + } + throw new IOException("Nameserver not responding on " + node.host() + + " when looking up " + node.alive()); + } catch (final OtpErlangDecodeException e) { + if (traceLevel >= traceThreshold) { + System.out.println("<- (invalid response)"); + } + throw new IOException("Nameserver not responding on " + node.host() + + " when looking up " + node.alive()); + } finally { + try { + if (s != null) { + s.close(); + } + } catch (final IOException e) { /* ignore close errors */ + } + s = null; + } + + if (traceLevel >= traceThreshold) { + if (port == 0) { + System.out.println("<- NOT FOUND"); + } else { + System.out.println("<- PORT " + port); + } + } + return port; + } + + private static int r4_lookupPort(final AbstractNode node) + throws IOException { + int port = 0; + Socket s = null; + + try { + final OtpOutputStream obuf = new OtpOutputStream(); + s = new Socket(node.host(), EpmdPort.get()); + + // build and send epmd request + // length[2], tag[1], alivename[n] (length = n+1) + obuf.write2BE(node.alive().length() + 1); + obuf.write1(port4req); + obuf.writeN(node.alive().getBytes()); + + // send request + obuf.writeTo(s.getOutputStream()); + + if (traceLevel >= traceThreshold) { + System.out.println("-> LOOKUP (r4) " + node); + } + + // receive and decode reply + // resptag[1], result[1], port[2], ntype[1], proto[1], + // disthigh[2], distlow[2], nlen[2], alivename[n], + // elen[2], edata[m] + final byte[] tmpbuf = new byte[100]; + + final int n = s.getInputStream().read(tmpbuf); + + if (n < 0) { + // this was an r3 node => not a failure (yet) + + s.close(); + throw new IOException("Nameserver not responding on " + + node.host() + " when looking up " + node.alive()); + } + + final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0); + + final int response = ibuf.read1(); + if (response == port4resp) { + final int result = ibuf.read1(); + if (result == 0) { + port = ibuf.read2BE(); + + node.ntype = ibuf.read1(); + node.proto = ibuf.read1(); + node.distHigh = ibuf.read2BE(); + node.distLow = ibuf.read2BE(); + // ignore rest of fields + } + } + } catch (final IOException e) { + if (traceLevel >= traceThreshold) { + System.out.println("<- (no response)"); + } + throw new IOException("Nameserver not responding on " + node.host() + + " when looking up " + node.alive()); + } catch (final OtpErlangDecodeException e) { + if (traceLevel >= traceThreshold) { + System.out.println("<- (invalid response)"); + } + throw new IOException("Nameserver not responding on " + node.host() + + " when looking up " + node.alive()); + } finally { + try { + if (s != null) { + s.close(); + } + } catch (final IOException e) { /* ignore close errors */ + } + s = null; + } + + if (traceLevel >= traceThreshold) { + if (port == 0) { + System.out.println("<- NOT FOUND"); + } else { + System.out.println("<- PORT " + port); + } + } + return port; + } + + private static Socket r3_publish(final OtpLocalNode node) + throws IOException { + Socket s = null; + + try { + final OtpOutputStream obuf = new OtpOutputStream(); + s = new Socket((String) null, EpmdPort.get()); + + obuf.write2BE(node.alive().length() + 3); + + obuf.write1(publish3req); + obuf.write2BE(node.port()); + obuf.writeN(node.alive().getBytes()); + + // send request + obuf.writeTo(s.getOutputStream()); + if (traceLevel >= traceThreshold) { + System.out.println("-> PUBLISH (r3) " + node + " port=" + + node.port()); + } + + final byte[] tmpbuf = new byte[100]; + + final int n = s.getInputStream().read(tmpbuf); + + if (n < 0) { + s.close(); + if (traceLevel >= traceThreshold) { + System.out.println("<- (no response)"); + } + return null; + } + + final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0); + + if (ibuf.read1() == publish3ok) { + node.creation = ibuf.read2BE(); + if (traceLevel >= traceThreshold) { + System.out.println("<- OK"); + } + return s; // success - don't close socket + } + } catch (final IOException e) { + // epmd closed the connection = fail + if (s != null) { + s.close(); + } + if (traceLevel >= traceThreshold) { + System.out.println("<- (no response)"); + } + throw new IOException("Nameserver not responding on " + node.host() + + " when publishing " + node.alive()); + } catch (final OtpErlangDecodeException e) { + if (s != null) { + s.close(); + } + if (traceLevel >= traceThreshold) { + System.out.println("<- (invalid response)"); + } + throw new IOException("Nameserver not responding on " + node.host() + + " when publishing " + node.alive()); + } + + if (s != null) { + s.close(); + } + return null; // failure + } + + /* + * this function will get an exception if it tries to talk to an r3 epmd, or + * if something else happens that it cannot forsee. In both cases we return + * an exception (and the caller should try again, using the r3 protocol). If + * we manage to successfully communicate with an r4 epmd, we return either + * the socket, or null, depending on the result. + */ + private static Socket r4_publish(final OtpLocalNode node) + throws IOException { + Socket s = null; + + try { + final OtpOutputStream obuf = new OtpOutputStream(); + s = new Socket((String) null, EpmdPort.get()); + + obuf.write2BE(node.alive().length() + 13); + + obuf.write1(publish4req); + obuf.write2BE(node.port()); + + obuf.write1(node.type()); + + obuf.write1(node.proto()); + obuf.write2BE(node.distHigh()); + obuf.write2BE(node.distLow()); + + obuf.write2BE(node.alive().length()); + obuf.writeN(node.alive().getBytes()); + obuf.write2BE(0); // No extra + + // send request + obuf.writeTo(s.getOutputStream()); + + if (traceLevel >= traceThreshold) { + System.out.println("-> PUBLISH (r4) " + node + " port=" + + node.port()); + } + + // get reply + final byte[] tmpbuf = new byte[100]; + final int n = s.getInputStream().read(tmpbuf); + + if (n < 0) { + // this was an r3 node => not a failure (yet) + if (s != null) { + s.close(); + } + throw new IOException("Nameserver not responding on " + + node.host() + " when publishing " + node.alive()); + } + + final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0); + + final int response = ibuf.read1(); + if (response == publish4resp) { + final int result = ibuf.read1(); + if (result == 0) { + node.creation = ibuf.read2BE(); + if (traceLevel >= traceThreshold) { + System.out.println("<- OK"); + } + return s; // success + } + } + } catch (final IOException e) { + // epmd closed the connection = fail + if (s != null) { + s.close(); + } + if (traceLevel >= traceThreshold) { + System.out.println("<- (no response)"); + } + throw new IOException("Nameserver not responding on " + node.host() + + " when publishing " + node.alive()); + } catch (final OtpErlangDecodeException e) { + if (s != null) { + s.close(); + } + if (traceLevel >= traceThreshold) { + System.out.println("<- (invalid response)"); + } + throw new IOException("Nameserver not responding on " + node.host() + + " when publishing " + node.alive()); + } + + if (s != null) { + s.close(); + } + return null; + } + + public static String[] lookupNames() throws IOException { + return lookupNames(InetAddress.getLocalHost()); + } + + public static String[] lookupNames(final InetAddress address) + throws IOException { + Socket s = null; + + try { + final OtpOutputStream obuf = new OtpOutputStream(); + try { + s = new Socket(address, EpmdPort.get()); + + obuf.write2BE(1); + obuf.write1(names4req); + // send request + obuf.writeTo(s.getOutputStream()); + + if (traceLevel >= traceThreshold) { + System.out.println("-> NAMES (r4) "); + } + + // get reply + final byte[] buffer = new byte[256]; + final ByteArrayOutputStream out = new ByteArrayOutputStream(256); + while (true) { + final int bytesRead = s.getInputStream().read(buffer); + if (bytesRead == -1) { + break; + } + out.write(buffer, 0, bytesRead); + } + final byte[] tmpbuf = out.toByteArray(); + final OtpInputStream ibuf = new OtpInputStream(tmpbuf, 0); + ibuf.read4BE(); // read port int + // final int port = ibuf.read4BE(); + // check if port = epmdPort + + final int n = tmpbuf.length; + final byte[] buf = new byte[n - 4]; + System.arraycopy(tmpbuf, 4, buf, 0, n - 4); + final String all = OtpErlangString.newString(buf); + return all.split("\n"); + } finally { + if (s != null) { + s.close(); + } + } + + } catch (final IOException e) { + if (traceLevel >= traceThreshold) { + System.out.println("<- (no response)"); + } + throw new IOException( + "Nameserver not responding when requesting names"); + } catch (final OtpErlangDecodeException e) { + if (traceLevel >= traceThreshold) { + System.out.println("<- (invalid response)"); + } + throw new IOException( + "Nameserver not responding when requesting names"); + } + } + +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangAtom.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangAtom.java new file mode 100644 index 0000000000..4d53447164 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangAtom.java @@ -0,0 +1,284 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang atoms. Atoms can be created from + * strings whose length is not more than {@link #maxAtomLength maxAtomLength} + * characters. + */ +public class OtpErlangAtom extends OtpErlangObject implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = -3204386396807876641L; + + /** The maximun allowed length of an atom, in characters */ + public static final int maxAtomLength = 0xff; // one byte length + + private final String atom; + + /** + * Create an atom from the given string. + * + * @param atom + * the string to create the atom from. + * + * @exception java.lang.IllegalArgumentException + * if the string is null or contains more than + * {@link #maxAtomLength maxAtomLength} characters. + */ + public OtpErlangAtom(final String atom) { + if (atom == null) { + throw new java.lang.IllegalArgumentException( + "null string value"); + } + + if (atom.length() > maxAtomLength) { + throw new java.lang.IllegalArgumentException("Atom may not exceed " + + maxAtomLength + " characters"); + } + this.atom = atom; + } + + /** + * Create an atom from a stream containing an atom encoded in Erlang + * external format. + * + * @param buf + * the stream containing the encoded atom. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang atom. + */ + public OtpErlangAtom(final OtpInputStream buf) + throws OtpErlangDecodeException { + atom = buf.read_atom(); + } + + /** + * Create an atom whose value is "true" or "false". + */ + public OtpErlangAtom(final boolean t) { + atom = String.valueOf(t); + } + + /** + * Get the actual string contained in this object. + * + * @return the raw string contained in this object, without regard to Erlang + * quoting rules. + * + * @see #toString + */ + public String atomValue() { + return atom; + } + + /** + * The boolean value of this atom. + * + * @return the value of this atom expressed as a boolean value. If the atom + * consists of the characters "true" (independent of case) the value + * will be true. For any other values, the value will be false. + * + */ + public boolean booleanValue() { + return Boolean.valueOf(atomValue()).booleanValue(); + } + + /** + * Get the printname of the atom represented by this object. The difference + * between this method and {link #atomValue atomValue()} is that the + * printname is quoted and escaped where necessary, according to the Erlang + * rules for atom naming. + * + * @return the printname representation of this atom object. + * + * @see #atomValue + */ + @Override + public String toString() { + if (atomNeedsQuoting(atom)) { + return "'" + escapeSpecialChars(atom) + "'"; + } else { + return atom; + } + } + + /** + * Determine if two atoms are equal. + * + * @param o + * the other object to compare to. + * + * @return true if the atoms are equal, false otherwise. + */ + @Override + public boolean equals(final Object o) { + + if (!(o instanceof OtpErlangAtom)) { + return false; + } + + final OtpErlangAtom atom = (OtpErlangAtom) o; + return this.atom.compareTo(atom.atom) == 0; + } + + @Override + protected int doHashCode() { + return atom.hashCode(); + } + + /** + * Convert this atom to the equivalent Erlang external representation. + * + * @param buf + * an output stream to which the encoded atom should be + * written. + */ + @Override + public void encode(final OtpOutputStream buf) { + buf.write_atom(atom); + } + + /* the following four predicates are helpers for the toString() method */ + private boolean isErlangDigit(final char c) { + return c >= '0' && c <= '9'; + } + + private boolean isErlangUpper(final char c) { + return c >= 'A' && c <= 'Z' || c == '_'; + } + + private boolean isErlangLower(final char c) { + return c >= 'a' && c <= 'z'; + } + + private boolean isErlangLetter(final char c) { + return isErlangLower(c) || isErlangUpper(c); + } + + // true if the atom should be displayed with quotation marks + private boolean atomNeedsQuoting(final String s) { + char c; + + if (s.length() == 0) { + return true; + } + if (!isErlangLower(s.charAt(0))) { + return true; + } + + final int len = s.length(); + for (int i = 1; i < len; i++) { + c = s.charAt(i); + + if (!isErlangLetter(c) && !isErlangDigit(c) && c != '@') { + return true; + } + } + return false; + } + + /* + * Get the atom string, with special characters escaped. Note that this + * function currently does not consider any characters above 127 to be + * printable. + */ + private String escapeSpecialChars(final String s) { + char c; + final StringBuffer so = new StringBuffer(); + + final int len = s.length(); + for (int i = 0; i < len; i++) { + c = s.charAt(i); + + /* + * note that some of these escape sequences are unique to Erlang, + * which is why the corresponding 'case' values use octal. The + * resulting string is, of course, in Erlang format. + */ + + switch (c) { + // some special escape sequences + case '\b': + so.append("\\b"); + break; + + case 0177: + so.append("\\d"); + break; + + case 033: + so.append("\\e"); + break; + + case '\f': + so.append("\\f"); + break; + + case '\n': + so.append("\\n"); + break; + + case '\r': + so.append("\\r"); + break; + + case '\t': + so.append("\\t"); + break; + + case 013: + so.append("\\v"); + break; + + case '\\': + so.append("\\\\"); + break; + + case '\'': + so.append("\\'"); + break; + + case '\"': + so.append("\\\""); + break; + + default: + // some other character classes + if (c < 027) { + // control chars show as "\^@", "\^A" etc + so.append("\\^" + (char) ('A' - 1 + c)); + } else if (c > 126) { + // 8-bit chars show as \345 \344 \366 etc + so.append("\\" + Integer.toOctalString(c)); + } else { + // character is printable without modification! + so.append(c); + } + } + } + return new String(so); + } + +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBinary.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBinary.java new file mode 100644 index 0000000000..a9eaad540e --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBinary.java @@ -0,0 +1,88 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang binaries. Anything that can be + * represented as a sequence of bytes can be made into an Erlang binary. + */ +public class OtpErlangBinary extends OtpErlangBitstr implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = -3781009633593609217L; + + /** + * Create a binary from a byte array + * + * @param bin + * the array of bytes from which to create the binary. + */ + public OtpErlangBinary(final byte[] bin) { + super(bin); + } + + /** + * Create a binary from a stream containing a binary encoded in Erlang + * external format. + * + * @param buf + * the stream containing the encoded binary. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang binary. + */ + public OtpErlangBinary(final OtpInputStream buf) + throws OtpErlangDecodeException { + super(new byte[0]); + bin = buf.read_binary(); + pad_bits = 0; + } + + /** + * Create a binary from an arbitrary Java Object. The object must implement + * java.io.Serializable or java.io.Externalizable. + * + * @param o + * the object to serialize and create this binary from. + */ + public OtpErlangBinary(final Object o) { + super(o); + } + + /** + * Convert this binary to the equivalent Erlang external representation. + * + * @param buf + * an output stream to which the encoded binary should be + * written. + */ + @Override + public void encode(final OtpOutputStream buf) { + buf.write_binary(bin); + } + + @Override + public Object clone() { + final OtpErlangBinary that = (OtpErlangBinary) super.clone(); + return that; + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBitstr.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBitstr.java new file mode 100644 index 0000000000..97897fe182 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBitstr.java @@ -0,0 +1,285 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2007-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang bitstrs. An Erlang bitstr is an + * Erlang binary with a length not an integral number of bytes (8-bit). Anything + * can be represented as a sequence of bytes can be made into an Erlang bitstr. + */ +public class OtpErlangBitstr extends OtpErlangObject implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = -3781009633593609217L; + + protected byte[] bin; + protected int pad_bits; + + /** + * Create a bitstr from a byte array + * + * @param bin + * the array of bytes from which to create the bitstr. + */ + public OtpErlangBitstr(final byte[] bin) { + this.bin = new byte[bin.length]; + System.arraycopy(bin, 0, this.bin, 0, bin.length); + pad_bits = 0; + } + + /** + * Create a bitstr with pad bits from a byte array. + * + * @param bin + * the array of bytes from which to create the bitstr. + * @param pad_bits + * the number of unused bits in the low end of the last byte. + */ + public OtpErlangBitstr(final byte[] bin, final int pad_bits) { + this.bin = new byte[bin.length]; + System.arraycopy(bin, 0, this.bin, 0, bin.length); + this.pad_bits = pad_bits; + + check_bitstr(this.bin, this.pad_bits); + } + + private void check_bitstr(final byte[] bin, final int pad_bits) { + if (pad_bits < 0 || 7 < pad_bits) { + throw new java.lang.IllegalArgumentException( + "Padding must be in range 0..7"); + } + if (pad_bits != 0 && bin.length == 0) { + throw new java.lang.IllegalArgumentException( + "Padding on zero length bitstr"); + } + if (bin.length != 0) { + // Make sure padding is zero + bin[bin.length - 1] &= ~((1 << pad_bits) - 1); + } + } + + /** + * Create a bitstr from a stream containing a bitstr encoded in Erlang + * external format. + * + * @param buf + * the stream containing the encoded bitstr. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang bitstr. + */ + public OtpErlangBitstr(final OtpInputStream buf) + throws OtpErlangDecodeException { + final int pbs[] = { 0 }; // This is ugly just to get a value-result + // parameter + bin = buf.read_bitstr(pbs); + pad_bits = pbs[0]; + + check_bitstr(bin, pad_bits); + } + + /** + * Create a bitstr from an arbitrary Java Object. The object must implement + * java.io.Serializable or java.io.Externalizable. + * + * @param o + * the object to serialize and create this bitstr from. + */ + public OtpErlangBitstr(final Object o) { + try { + bin = toByteArray(o); + pad_bits = 0; + } catch (final IOException e) { + throw new java.lang.IllegalArgumentException( + "Object must implement Serializable"); + } + } + + private static byte[] toByteArray(final Object o) + throws java.io.IOException { + + if (o == null) { + return null; + } + + /* need to synchronize use of the shared baos */ + final java.io.ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream( + baos); + + oos.writeObject(o); + oos.flush(); + + return baos.toByteArray(); + } + + private static Object fromByteArray(final byte[] buf) { + if (buf == null) { + return null; + } + + try { + final java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream( + buf); + final java.io.ObjectInputStream ois = new java.io.ObjectInputStream( + bais); + return ois.readObject(); + } catch (final java.lang.ClassNotFoundException e) { + } catch (final java.io.IOException e) { + } + + return null; + } + + /** + * Get the byte array from a bitstr, padded with zero bits in the little end + * of the last byte. + * + * @return the byte array containing the bytes for this bitstr. + */ + public byte[] binaryValue() { + return bin; + } + + /** + * Get the size in whole bytes of the bitstr, rest bits in the last byte not + * counted. + * + * @return the number of bytes contained in the bintstr. + */ + public int size() { + if (pad_bits == 0) { + return bin.length; + } + if (bin.length == 0) { + throw new java.lang.IllegalStateException("Impossible length"); + } + return bin.length - 1; + } + + /** + * Get the number of pad bits in the last byte of the bitstr. The pad bits + * are zero and in the little end. + * + * @return the number of pad bits in the bitstr. + */ + public int pad_bits() { + return pad_bits; + } + + /** + * Get the java Object from the bitstr. If the bitstr contains a serialized + * Java object, then this method will recreate the object. + * + * + * @return the java Object represented by this bitstr, or null if the bitstr + * does not represent a Java Object. + */ + public Object getObject() { + if (pad_bits != 0) { + return null; + } + return fromByteArray(bin); + } + + /** + * Get the string representation of this bitstr object. A bitstr is printed + * as #Bin<N>, where N is the number of bytes contained in the object + * or #bin<N-M> if there are M pad bits. + * + * @return the Erlang string representation of this bitstr. + */ + @Override + public String toString() { + if (pad_bits == 0) { + return "#Bin<" + bin.length + ">"; + } + if (bin.length == 0) { + throw new java.lang.IllegalStateException("Impossible length"); + } + return "#Bin<" + bin.length + "-" + pad_bits + ">"; + } + + /** + * Convert this bitstr to the equivalent Erlang external representation. + * + * @param buf + * an output stream to which the encoded bitstr should be + * written. + */ + @Override + public void encode(final OtpOutputStream buf) { + buf.write_bitstr(bin, pad_bits); + } + + /** + * Determine if two bitstrs are equal. Bitstrs are equal if they have the + * same byte length and tail length, and the array of bytes is identical. + * + * @param o + * the bitstr to compare to. + * + * @return true if the bitstrs contain the same bits, false otherwise. + */ + @Override + public boolean equals(final Object o) { + if (!(o instanceof OtpErlangBitstr)) { + return false; + } + + final OtpErlangBitstr that = (OtpErlangBitstr) o; + if (pad_bits != that.pad_bits) { + return false; + } + + final int len = bin.length; + if (len != that.bin.length) { + return false; + } + + for (int i = 0; i < len; i++) { + if (bin[i] != that.bin[i]) { + return false; // early exit + } + } + + return true; + } + + @Override + protected int doHashCode() { + OtpErlangObject.Hash hash = new OtpErlangObject.Hash(15); + hash.combine(bin); + hash.combine(pad_bits); + return hash.valueOf(); + } + + @Override + public Object clone() { + final OtpErlangBitstr that = (OtpErlangBitstr) super.clone(); + that.bin = bin.clone(); + that.pad_bits = pad_bits; + return that; + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBoolean.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBoolean.java new file mode 100644 index 0000000000..b97b5b7d90 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangBoolean.java @@ -0,0 +1,56 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang booleans, which are special cases of + * atoms with values 'true' and 'false'. + */ +public class OtpErlangBoolean extends OtpErlangAtom implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = 1087178844844988393L; + + /** + * Create a boolean from the given value + * + * @param t + * the boolean value to represent as an atom. + */ + public OtpErlangBoolean(final boolean t) { + super(t); + } + + /** + * Create a boolean from a stream containing an atom encoded in Erlang + * external format. The value of the boolean will be true if the atom + * represented by the stream is "true" without regard to case. For other + * atom values, the boolean will have the value false. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang atom. + */ + public OtpErlangBoolean(final OtpInputStream buf) + throws OtpErlangDecodeException { + super(buf); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangByte.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangByte.java new file mode 100644 index 0000000000..2d598c119e --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangByte.java @@ -0,0 +1,61 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang integral types. + */ +public class OtpErlangByte extends OtpErlangLong implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = 5778019796466613446L; + + /** + * Create an Erlang integer from the given value. + * + * @param b + * the byte value to use. + */ + public OtpErlangByte(final byte b) { + super(b); + } + + /** + * Create an Erlang integer from a stream containing an integer encoded in + * Erlang external format. + * + * @param buf + * the stream containing the encoded value. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang integer. + * + * @exception OtpErlangRangeException + * if the value is too large to be represented as a byte. + */ + public OtpErlangByte(final OtpInputStream buf) + throws OtpErlangRangeException, OtpErlangDecodeException { + super(buf); + + final byte i = byteValue(); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangChar.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangChar.java new file mode 100644 index 0000000000..b442bbcec1 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangChar.java @@ -0,0 +1,61 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang integral types. + */ +public class OtpErlangChar extends OtpErlangLong implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = 3225337815669398204L; + + /** + * Create an Erlang integer from the given value. + * + * @param c + * the char value to use. + */ + public OtpErlangChar(final char c) { + super(c); + } + + /** + * Create an Erlang integer from a stream containing an integer encoded in + * Erlang external format. + * + * @param buf + * the stream containing the encoded value. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang integer. + * + * @exception OtpErlangRangeException + * if the value is too large to be represented as a char. + */ + public OtpErlangChar(final OtpInputStream buf) + throws OtpErlangRangeException, OtpErlangDecodeException { + super(buf); + + final char i = charValue(); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangDecodeException.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangDecodeException.java new file mode 100644 index 0000000000..db55deaedf --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangDecodeException.java @@ -0,0 +1,35 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +/** + * Exception raised when an attempt is made to create an Erlang term by decoding + * a sequence of bytes that does not represent the type of term that was + * requested. + * + * @see OtpInputStream + */ +public class OtpErlangDecodeException extends OtpErlangException { + /** + * Provides a detailed message. + */ + public OtpErlangDecodeException(final String msg) { + super(msg); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangDouble.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangDouble.java new file mode 100644 index 0000000000..793940e858 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangDouble.java @@ -0,0 +1,132 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang floats and doubles. Erlang defines + * only one floating point numeric type, however this class and its subclass + * {@link OtpErlangFloat} are used to provide representations corresponding to + * the Java types Double and Float. + */ +public class OtpErlangDouble extends OtpErlangObject implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = 132947104811974021L; + + private final double d; + + /** + * Create an Erlang float from the given double value. + */ + public OtpErlangDouble(final double d) { + this.d = d; + } + + /** + * Create an Erlang float from a stream containing a double encoded in + * Erlang external format. + * + * @param buf + * the stream containing the encoded value. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang float. + */ + public OtpErlangDouble(final OtpInputStream buf) + throws OtpErlangDecodeException { + d = buf.read_double(); + } + + /** + * Get the value, as a double. + * + * @return the value of this object, as a double. + */ + public double doubleValue() { + return d; + } + + /** + * Get the value, as a float. + * + * @return the value of this object, as a float. + * + * @exception OtpErlangRangeException + * if the value cannot be represented as a float. + */ + public float floatValue() throws OtpErlangRangeException { + final float f = (float) d; + + if (f != d) { + throw new OtpErlangRangeException("Value too large for float: " + d); + } + + return f; + } + + /** + * Get the string representation of this double. + * + * @return the string representation of this double. + */ + @Override + public String toString() { + return "" + d; + } + + /** + * Convert this double to the equivalent Erlang external representation. + * + * @param buf + * an output stream to which the encoded value should be + * written. + */ + @Override + public void encode(final OtpOutputStream buf) { + buf.write_double(d); + } + + /** + * Determine if two floats are equal. Floats are equal if they contain the + * same value. + * + * @param o + * the float to compare to. + * + * @return true if the floats have the same value. + */ + @Override + public boolean equals(final Object o) { + if (!(o instanceof OtpErlangDouble)) { + return false; + } + + final OtpErlangDouble d = (OtpErlangDouble) o; + return this.d == d.d; + } + + @Override + protected int doHashCode() { + Double v = new Double(d); + return v.hashCode(); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangException.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangException.java new file mode 100644 index 0000000000..5b111a56a8 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangException.java @@ -0,0 +1,40 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +/** + * Base class for the other OTP erlang exception classes. + */ +public class OtpErlangException extends OtpException { + private static final long serialVersionUID = 1L; + + /** + * Provides no message. + */ + public OtpErlangException() { + super(); + } + + /** + * Provides a detailed message. + */ + public OtpErlangException(final String msg) { + super(msg); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangExit.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangExit.java new file mode 100644 index 0000000000..6b9015c0e5 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangExit.java @@ -0,0 +1,112 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +/** + * Exception raised when a communication channel is broken. This can be caused + * for a number of reasons, for example: + * + * <ul> + * <li> an error in communication has occurred + * <li> a remote process has sent an exit signal + * <li> a linked process has exited + * </ul> + * + * @see OtpConnection + */ + +public class OtpErlangExit extends OtpErlangException { + private static final long serialVersionUID = 1L; + + OtpErlangObject reason = null; + OtpErlangPid pid = null; + + /** + * Create an OtpErlangExit exception with the given reason. + * + * @param reason + * the reason this exit signal has been sent. + */ + public OtpErlangExit(final OtpErlangObject reason) { + super(reason.toString()); + this.reason = reason; + } + + /** + * <p> + * Equivalent to <code>OtpErlangExit(new + * OtpErlangAtom(reason)</code>. + * </p> + * + * @param reason + * the reason this exit signal has been sent. + * + * @see #OtpErlangExit(OtpErlangObject) + */ + public OtpErlangExit(final String reason) { + this(new OtpErlangAtom(reason)); + } + + /** + * Create an OtpErlangExit exception with the given reason and sender pid. + * + * @param reason + * the reason this exit signal has been sent. + * + * @param pid + * the pid that sent this exit. + */ + public OtpErlangExit(final OtpErlangObject reason, final OtpErlangPid pid) { + super(reason.toString()); + this.reason = reason; + this.pid = pid; + } + + /** + * <p> + * Equivalent to <code>OtpErlangExit(new OtpErlangAtom(reason), + * pid)</code>. + * </p> + * + * @param reason + * the reason this exit signal has been sent. + * + * @param pid + * the pid that sent this exit. + * + * @see #OtpErlangExit(OtpErlangObject, OtpErlangPid) + */ + public OtpErlangExit(final String reason, final OtpErlangPid pid) { + this(new OtpErlangAtom(reason), pid); + } + + /** + * Get the reason associated with this exit signal. + */ + public OtpErlangObject reason() { + return reason; + } + + /** + * Get the pid that sent this exit. + */ + public OtpErlangPid pid() { + return pid; + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangExternalFun.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangExternalFun.java new file mode 100644 index 0000000000..09f36b1ff4 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangExternalFun.java @@ -0,0 +1,73 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +public class OtpErlangExternalFun extends OtpErlangObject { + // don't change this! + private static final long serialVersionUID = 6443965570641913886L; + + private final String module; + private final String function; + private final int arity; + + public OtpErlangExternalFun(final String module, final String function, + final int arity) { + super(); + this.module = module; + this.function = function; + this.arity = arity; + } + + public OtpErlangExternalFun(final OtpInputStream buf) + throws OtpErlangDecodeException { + final OtpErlangExternalFun f = buf.read_external_fun(); + module = f.module; + function = f.function; + arity = f.arity; + } + + @Override + public void encode(final OtpOutputStream buf) { + buf.write_external_fun(module, function, arity); + } + + @Override + public boolean equals(final Object o) { + if (!(o instanceof OtpErlangExternalFun)) { + return false; + } + final OtpErlangExternalFun f = (OtpErlangExternalFun) o; + return module.equals(f.module) && function.equals(f.function) + && arity == f.arity; + } + + @Override + protected int doHashCode() { + OtpErlangObject.Hash hash = new OtpErlangObject.Hash(14); + hash.combine(module.hashCode(), function.hashCode()); + hash.combine(arity); + return hash.valueOf(); + } + + @Override + public String toString() { + return "#Fun<" + module + "." + function + "." + arity + ">"; + } + +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangFloat.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangFloat.java new file mode 100644 index 0000000000..8662b74c53 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangFloat.java @@ -0,0 +1,58 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang floats and doubles. + */ +public class OtpErlangFloat extends OtpErlangDouble implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = -2231546377289456934L; + + /** + * Create an Erlang float from the given float value. + */ + public OtpErlangFloat(final float f) { + super(f); + } + + /** + * Create an Erlang float from a stream containing a float encoded in Erlang + * external format. + * + * @param buf + * the stream containing the encoded value. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang float. + * + * @exception OtpErlangRangeException + * if the value cannot be represented as a Java float. + */ + public OtpErlangFloat(final OtpInputStream buf) + throws OtpErlangDecodeException, OtpErlangRangeException { + super(buf); + + final float f = floatValue(); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangFun.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangFun.java new file mode 100644 index 0000000000..fc104e9564 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangFun.java @@ -0,0 +1,131 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; +import java.util.Arrays; + +public class OtpErlangFun extends OtpErlangObject implements Serializable { + // don't change this! + private static final long serialVersionUID = -3423031125356706472L; + + private final OtpErlangPid pid; + private final String module; + private final long index; + private final long old_index; + private final long uniq; + private final OtpErlangObject[] freeVars; + private final int arity; + private final byte[] md5; + + public OtpErlangFun(final OtpInputStream buf) + throws OtpErlangDecodeException { + final OtpErlangFun f = buf.read_fun(); + pid = f.pid; + module = f.module; + arity = f.arity; + md5 = f.md5; + index = f.index; + old_index = f.old_index; + uniq = f.uniq; + freeVars = f.freeVars; + } + + public OtpErlangFun(final OtpErlangPid pid, final String module, + final long index, final long uniq, final OtpErlangObject[] freeVars) { + this.pid = pid; + this.module = module; + arity = -1; + md5 = null; + this.index = index; + old_index = 0; + this.uniq = uniq; + this.freeVars = freeVars; + } + + public OtpErlangFun(final OtpErlangPid pid, final String module, + final int arity, final byte[] md5, final int index, + final long old_index, final long uniq, + final OtpErlangObject[] freeVars) { + this.pid = pid; + this.module = module; + this.arity = arity; + this.md5 = md5; + this.index = index; + this.old_index = old_index; + this.uniq = uniq; + this.freeVars = freeVars; + } + + @Override + public void encode(final OtpOutputStream buf) { + buf + .write_fun(pid, module, old_index, arity, md5, index, uniq, + freeVars); + } + + @Override + public boolean equals(final Object o) { + if (!(o instanceof OtpErlangFun)) { + return false; + } + final OtpErlangFun f = (OtpErlangFun) o; + if (!pid.equals(f.pid) || !module.equals(f.module) || arity != f.arity) { + return false; + } + if (md5 == null) { + if (f.md5 != null) { + return false; + } + } else { + if (!md5.equals(f.md5)) { + return false; + } + } + if (index != f.index || uniq != f.uniq) { + return false; + } + if (freeVars == null) { + return f.freeVars == null; + } + return freeVars.equals(f.freeVars); + } + + @Override + protected int doHashCode() { + OtpErlangObject.Hash hash = new OtpErlangObject.Hash(1); + hash.combine(pid.hashCode(), module.hashCode()); + hash.combine(arity); + if (md5 != null) hash.combine(md5); + hash.combine(index); + hash.combine(uniq); + if (freeVars != null) { + for (OtpErlangObject o: freeVars) { + hash.combine(o.hashCode(), 1); + } + } + return hash.valueOf(); + } + + @Override + public String toString() { + return "#Fun<" + module + "." + old_index + "." + uniq + ">"; + } + +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangInt.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangInt.java new file mode 100644 index 0000000000..d947421459 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangInt.java @@ -0,0 +1,61 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang integral types. + */ +public class OtpErlangInt extends OtpErlangLong implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = 1229430977614805556L; + + /** + * Create an Erlang integer from the given value. + * + * @param i + * the int value to use. + */ + public OtpErlangInt(final int i) { + super(i); + } + + /** + * Create an Erlang integer from a stream containing an integer encoded in + * Erlang external format. + * + * @param buf + * the stream containing the encoded value. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang integer. + * + * @exception OtpErlangRangeException + * if the value is too large to be represented as an int. + */ + public OtpErlangInt(final OtpInputStream buf) + throws OtpErlangRangeException, OtpErlangDecodeException { + super(buf); + + final int j = intValue(); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangList.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangList.java new file mode 100644 index 0000000000..3456fd7412 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangList.java @@ -0,0 +1,505 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Provides a Java representation of Erlang lists. Lists are created from zero + * or more arbitrary Erlang terms. + * + * <p> + * The arity of the list is the number of elements it contains. + */ +public class OtpErlangList extends OtpErlangObject implements + Iterable<OtpErlangObject>, Serializable, Cloneable { + // don't change this! + static final long serialVersionUID = 5999112769036676548L; + + private static final OtpErlangObject[] NO_ELEMENTS = new OtpErlangObject[0]; + + final private OtpErlangObject[] elems; + + private OtpErlangObject lastTail = null; + + /** + * Create an empty list. + */ + public OtpErlangList() { + elems = NO_ELEMENTS; + } + + /** + * Create a list of Erlang integers representing Unicode codePoints. + * This method does not check if the string contains valid code points. + * + * @param str + * the characters from which to create the list. + */ + public OtpErlangList(final String str) { + if (str == null || str.length() == 0) { + elems = NO_ELEMENTS; + } else { + final int[] codePoints = OtpErlangString.stringToCodePoints(str); + elems = new OtpErlangObject[codePoints.length]; + for (int i = 0; i < elems.length; i++) { + elems[i] = new OtpErlangInt(codePoints[i]); + } + } + } + + /** + * Create a list containing one element. + * + * @param elem + * the elememet to make the list from. + */ + public OtpErlangList(final OtpErlangObject elem) { + elems = new OtpErlangObject[] { elem }; + } + + /** + * Create a list from an array of arbitrary Erlang terms. + * + * @param elems + * the array of terms from which to create the list. + */ + public OtpErlangList(final OtpErlangObject[] elems) { + this(elems, 0, elems.length); + } + + /** + * Create a list from an array of arbitrary Erlang terms. Tail can be + * specified, if not null, the list will not be proper. + * + * @param elems + * array of terms from which to create the list + * @param lastTail + * @throws OtpErlangException + */ + public OtpErlangList(final OtpErlangObject[] elems, + final OtpErlangObject lastTail) throws OtpErlangException { + this(elems, 0, elems.length); + if (elems.length == 0 && lastTail != null) { + throw new OtpErlangException("Bad list, empty head, non-empty tail"); + } + this.lastTail = lastTail; + } + + /** + * Create a list from an array of arbitrary Erlang terms. + * + * @param elems + * the array of terms from which to create the list. + * @param start + * the offset of the first term to insert. + * @param count + * the number of terms to insert. + */ + public OtpErlangList(final OtpErlangObject[] elems, final int start, + final int count) { + if (elems != null && count > 0) { + this.elems = new OtpErlangObject[count]; + System.arraycopy(elems, start, this.elems, 0, count); + } else { + this.elems = NO_ELEMENTS; + } + } + + /** + * Create a list from a stream containing an list encoded in Erlang external + * format. + * + * @param buf + * the stream containing the encoded list. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang list. + */ + public OtpErlangList(final OtpInputStream buf) + throws OtpErlangDecodeException { + final int arity = buf.read_list_head(); + if (arity > 0) { + elems = new OtpErlangObject[arity]; + for (int i = 0; i < arity; i++) { + elems[i] = buf.read_any(); + } + /* discard the terminating nil (empty list) or read tail */ + if (buf.peek1() == OtpExternal.nilTag) { + buf.read_nil(); + } else { + lastTail = buf.read_any(); + } + } else { + elems = NO_ELEMENTS; + } + } + + /** + * Get the arity of the list. + * + * @return the number of elements contained in the list. + */ + public int arity() { + return elems.length; + } + + /** + * Get the specified element from the list. + * + * @param i + * the index of the requested element. List elements are numbered + * as array elements, starting at 0. + * + * @return the requested element, of null if i is not a valid element index. + */ + public OtpErlangObject elementAt(final int i) { + if (i >= arity() || i < 0) { + return null; + } + return elems[i]; + } + + /** + * Get all the elements from the list as an array. + * + * @return an array containing all of the list's elements. + */ + public OtpErlangObject[] elements() { + if (arity() == 0) { + return NO_ELEMENTS; + } else { + final OtpErlangObject[] res = new OtpErlangObject[arity()]; + System.arraycopy(elems, 0, res, 0, res.length); + return res; + } + } + + /** + * Get the string representation of the list. + * + * @return the string representation of the list. + */ + + @Override + public String toString() { + return toString(0); + } + + protected String toString(final int start) { + final StringBuffer s = new StringBuffer(); + s.append("["); + + for (int i = start; i < arity(); i++) { + if (i > start) { + s.append(","); + } + s.append(elems[i].toString()); + } + if (lastTail != null) { + s.append("|").append(lastTail.toString()); + } + s.append("]"); + + return s.toString(); + } + + /** + * Convert this list to the equivalent Erlang external representation. Note + * that this method never encodes lists as strings, even when it is possible + * to do so. + * + * @param buf + * An output stream to which the encoded list should be written. + * + */ + + @Override + public void encode(final OtpOutputStream buf) { + encode(buf, 0); + } + + protected void encode(final OtpOutputStream buf, final int start) { + final int arity = arity() - start; + + if (arity > 0) { + buf.write_list_head(arity); + + for (int i = start; i < arity + start; i++) { + buf.write_any(elems[i]); + } + } + if (lastTail == null) { + buf.write_nil(); + } else { + buf.write_any(lastTail); + } + } + + /** + * Determine if two lists are equal. Lists are equal if they have the same + * arity and all of the elements are equal. + * + * @param o + * the list to compare to. + * + * @return true if the lists have the same arity and all the elements are + * equal. + */ + + @Override + public boolean equals(final Object o) { + + /* + * Be careful to use methods even for "this", so that equals work also + * for sublists + */ + + if (!(o instanceof OtpErlangList)) { + return false; + } + + final OtpErlangList l = (OtpErlangList) o; + + final int a = arity(); + if (a != l.arity()) { + return false; + } + for (int i = 0; i < a; i++) { + if (!elementAt(i).equals(l.elementAt(i))) { + return false; // early exit + } + } + final OtpErlangObject otherTail = l.getLastTail(); + if (getLastTail() == null && otherTail == null) { + return true; + } + if (getLastTail() == null) { + return false; + } + return getLastTail().equals(l.getLastTail()); + } + + public OtpErlangObject getLastTail() { + return lastTail; + } + + @Override + protected int doHashCode() { + OtpErlangObject.Hash hash = new OtpErlangObject.Hash(4); + final int a = arity(); + if (a == 0) { + return (int)3468870702L; + } + for (int i = 0; i < a; i++) { + hash.combine(elementAt(i).hashCode()); + } + final OtpErlangObject t = getLastTail(); + if (t != null) { + int h = t.hashCode(); + hash.combine(h, h); + } + return hash.valueOf(); + } + + @Override + public Object clone() { + try { + return new OtpErlangList(elements(), getLastTail()); + } catch (final OtpErlangException e) { + return null; + } + } + + public Iterator<OtpErlangObject> iterator() { + return iterator(0); + } + + private Iterator<OtpErlangObject> iterator(final int start) { + return new Itr(start); + } + + /** + * @return true if the list is proper, i.e. the last tail is nil + */ + public boolean isProper() { + return lastTail == null; + } + + public OtpErlangObject getHead() { + if (arity() > 0) { + return elems[0]; + } + return null; + } + + public OtpErlangObject getTail() { + return getNthTail(1); + } + + public OtpErlangObject getNthTail(final int n) { + final int arity = arity(); + if (arity >= n) { + if (arity == n && lastTail != null) { + return lastTail; + } else { + return new SubList(this, n); + } + } + return null; + } + + /** + * Convert a list of integers into a Unicode string, + * interpreting each integer as a Unicode code point value. + * + * @return A java.lang.String object created through its + * constructor String(int[], int, int). + * + * @exception OtpErlangException + * for non-proper and non-integer lists. + * + * @exception OtpErlangRangeException + * if any integer does not fit into a Java int. + * + * @exception java.security.InvalidParameterException + * if any integer is not within the Unicode range. + * + * @see String#String(int[], int, int) + * + */ + + public String stringValue() throws OtpErlangException { + if (! isProper()) { + throw new OtpErlangException("Non-proper list: " + this); + } + final int[] values = new int[arity()]; + for (int i = 0; i < values.length; ++i) { + final OtpErlangObject o = elementAt(i); + if (! (o instanceof OtpErlangLong)) { + throw new OtpErlangException("Non-integer term: " + o); + } + final OtpErlangLong l = (OtpErlangLong) o; + values[i] = l.intValue(); + } + return new String(values, 0, values.length); + } + + + + public static class SubList extends OtpErlangList { + private static final long serialVersionUID = OtpErlangList.serialVersionUID; + + private final int start; + + private final OtpErlangList parent; + + private SubList(final OtpErlangList parent, final int start) { + super(); + this.parent = parent; + this.start = start; + } + + @Override + public int arity() { + return parent.arity() - start; + } + + @Override + public OtpErlangObject elementAt(final int i) { + return parent.elementAt(i + start); + } + + @Override + public OtpErlangObject[] elements() { + final int n = parent.arity() - start; + final OtpErlangObject[] res = new OtpErlangObject[n]; + for (int i = 0; i < res.length; i++) { + res[i] = parent.elementAt(i + start); + } + return res; + } + + @Override + public boolean isProper() { + return parent.isProper(); + } + + @Override + public OtpErlangObject getHead() { + return parent.elementAt(start); + } + + @Override + public OtpErlangObject getNthTail(final int n) { + return parent.getNthTail(n + start); + } + + @Override + public String toString() { + return parent.toString(start); + } + + @Override + public void encode(final OtpOutputStream stream) { + parent.encode(stream, start); + } + + @Override + public OtpErlangObject getLastTail() { + return parent.getLastTail(); + } + + @Override + public Iterator<OtpErlangObject> iterator() { + return parent.iterator(start); + } + } + + private class Itr implements Iterator<OtpErlangObject> { + /** + * Index of element to be returned by subsequent call to next. + */ + private int cursor; + + private Itr(final int cursor) { + this.cursor = cursor; + } + + public boolean hasNext() { + return cursor < elems.length; + } + + public OtpErlangObject next() { + try { + return elems[cursor++]; + } catch (final IndexOutOfBoundsException e) { + throw new NoSuchElementException(); + } + } + + public void remove() { + throw new UnsupportedOperationException( + "OtpErlangList cannot be modified!"); + } + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangLong.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangLong.java new file mode 100644 index 0000000000..7e3e2a7296 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangLong.java @@ -0,0 +1,399 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; +import java.math.BigInteger; + +/** + * Provides a Java representation of Erlang integral types. Erlang does not + * distinguish between different integral types, however this class and its + * subclasses {@link OtpErlangByte}, {@link OtpErlangChar}, + * {@link OtpErlangInt}, and {@link OtpErlangShort} attempt to map the Erlang + * types onto the various Java integral types. Two additional classes, + * {@link OtpErlangUInt} and {@link OtpErlangUShort} are provided for Corba + * compatibility. See the documentation for IC for more information. + */ +public class OtpErlangLong extends OtpErlangObject implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = 1610466859236755096L; + + private long val; + private BigInteger bigVal = null; + + /** + * Create an Erlang integer from the given value. + * + * @param l + * the long value to use. + */ + public OtpErlangLong(final long l) { + val = l; + } + + /** + * Create an Erlang integer from the given value. + * + * @param val + * the long value to use. + */ + public OtpErlangLong(final BigInteger v) { + if (v == null) { + throw new java.lang.NullPointerException(); + } + if (v.bitLength() < 64) { + val = v.longValue(); + } else { + bigVal = v; + } + } + + /** + * Create an Erlang integer from a stream containing an integer encoded in + * Erlang external format. + * + * @param buf + * the stream containing the encoded value. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang integer. + */ + public OtpErlangLong(final OtpInputStream buf) + throws OtpErlangDecodeException { + final byte[] b = buf.read_integer_byte_array(); + try { + val = OtpInputStream.byte_array_to_long(b, false); + } catch (final OtpErlangDecodeException e) { + bigVal = new BigInteger(b); + } + } + + /** + * Get this number as a BigInteger. + * + * @return the value of this number, as a BigInteger. + */ + public BigInteger bigIntegerValue() { + if (bigVal != null) { + return bigVal; + } else { + return BigInteger.valueOf(val); + } + } + + /** + * Get this number as a long, or rather truncate all but the least + * significant 64 bits from the 2's complement representation of this number + * and return them as a long. + * + * @return the value of this number, as a long. + */ + public long longValue() { + if (bigVal != null) { + return bigVal.longValue(); + } else { + return val; + } + } + + /** + * Determine if this value can be represented as a long without truncation. + * + * @return true if this value fits in a long, false otherwise. + */ + public boolean isLong() { + // To just chech this.bigVal is a wee bit to simple, since + // there just might have be a mean bignum that arrived on + // a stream, and was a long disguised as more than 8 byte integer. + if (bigVal != null) { + return bigVal.bitLength() < 64; + } + return true; + } + + /** + * Determine if this value can be represented as an unsigned long without + * truncation, that is if the value is non-negative and its bit pattern + * completely fits in a long. + * + * @return true if this value is non-negative and fits in a long false + * otherwise. + */ + public boolean isULong() { + // Here we have the same problem as for isLong(), plus + // the whole range 1<<63 .. (1<<64-1) is allowed. + if (bigVal != null) { + return bigVal.signum() >= 0 && bigVal.bitLength() <= 64; + } + return val >= 0; + } + + /** + * Returns the number of bits in the minimal two's-complement representation + * of this BigInteger, excluding a sign bit. + * + * @return number of bits in the minimal two's-complement representation of + * this BigInteger, excluding a sign bit. + */ + public int bitLength() { + if (bigVal != null) { + return bigVal.bitLength(); + } + if (val == 0 || val == -1) { + return 0; + } else { + // Binary search for bit length + int i = 32; // mask length + long m = (1L << i) - 1; // AND mask with ones in little end + if (val < 0) { + m = ~m; // OR mask with ones in big end + for (int j = i >> 1; j > 0; j >>= 1) { // mask delta + if ((val | m) == val) { // mask >= enough + i -= j; + m >>= j; // try less bits + } else { + i += j; + m <<= j; // try more bits + } + } + if ((val | m) != val) { + i++; // mask < enough + } + } else { + for (int j = i >> 1; j > 0; j >>= 1) { // mask delta + if ((val & m) == val) { // mask >= enough + i -= j; + m >>= j; // try less bits + } else { + i += j; + m = m << j | m; // try more bits + } + } + if ((val & m) != val) { + i++; // mask < enough + } + } + return i; + } + } + + /** + * Return the signum function of this object. + * + * @return -1, 0 or 1 as the value is negative, zero or positive. + */ + public int signum() { + if (bigVal != null) { + return bigVal.signum(); + } else { + return val > 0 ? 1 : val < 0 ? -1 : 0; + } + } + + /** + * Get this number as an int. + * + * @return the value of this number, as an int. + * + * @exception OtpErlangRangeException + * if the value is too large to be represented as an int. + */ + public int intValue() throws OtpErlangRangeException { + final long l = longValue(); + final int i = (int) l; + + if (i != l) { + throw new OtpErlangRangeException("Value too large for int: " + val); + } + + return i; + } + + /** + * Get this number as a non-negative int. + * + * @return the value of this number, as an int. + * + * @exception OtpErlangRangeException + * if the value is too large to be represented as an int, + * or if the value is negative. + */ + public int uIntValue() throws OtpErlangRangeException { + final long l = longValue(); + final int i = (int) l; + + if (i != l) { + throw new OtpErlangRangeException("Value too large for int: " + val); + } else if (i < 0) { + throw new OtpErlangRangeException("Value not positive: " + val); + } + + return i; + } + + /** + * Get this number as a short. + * + * @return the value of this number, as a short. + * + * @exception OtpErlangRangeException + * if the value is too large to be represented as a + * short. + */ + public short shortValue() throws OtpErlangRangeException { + final long l = longValue(); + final short i = (short) l; + + if (i != l) { + throw new OtpErlangRangeException("Value too large for short: " + + val); + } + + return i; + } + + /** + * Get this number as a non-negative short. + * + * @return the value of this number, as a short. + * + * @exception OtpErlangRangeException + * if the value is too large to be represented as a + * short, or if the value is negative. + */ + public short uShortValue() throws OtpErlangRangeException { + final long l = longValue(); + final short i = (short) l; + + if (i != l) { + throw new OtpErlangRangeException("Value too large for short: " + + val); + } else if (i < 0) { + throw new OtpErlangRangeException("Value not positive: " + val); + } + + return i; + } + + /** + * Get this number as a char. + * + * @return the char value of this number. + * + * @exception OtpErlangRangeException + * if the value is too large to be represented as a char. + */ + public char charValue() throws OtpErlangRangeException { + final long l = longValue(); + final char i = (char) l; + + if (i != l) { + throw new OtpErlangRangeException("Value too large for char: " + + val); + } + + return i; + } + + /** + * Get this number as a byte. + * + * @return the byte value of this number. + * + * @exception OtpErlangRangeException + * if the value is too large to be represented as a byte. + */ + public byte byteValue() throws OtpErlangRangeException { + final long l = longValue(); + final byte i = (byte) l; + + if (i != l) { + throw new OtpErlangRangeException("Value too large for byte: " + + val); + } + + return i; + } + + /** + * Get the string representation of this number. + * + * @return the string representation of this number. + */ + @Override + public String toString() { + if (bigVal != null) { + return "" + bigVal; + } else { + return "" + val; + } + } + + /** + * Convert this number to the equivalent Erlang external representation. + * + * @param buf + * an output stream to which the encoded number should be + * written. + */ + @Override + public void encode(final OtpOutputStream buf) { + if (bigVal != null) { + buf.write_big_integer(bigVal); + } else { + buf.write_long(val); + } + } + + /** + * Determine if two numbers are equal. Numbers are equal if they contain the + * same value. + * + * @param o + * the number to compare to. + * + * @return true if the numbers have the same value. + */ + @Override + public boolean equals(final Object o) { + if (!(o instanceof OtpErlangLong)) { + return false; + } + + final OtpErlangLong that = (OtpErlangLong) o; + + if (bigVal != null && that.bigVal != null) { + return bigVal.equals(that.bigVal); + } else if (bigVal == null && that.bigVal == null) { + return val == that.val; + } + return false; + } + + @Override + protected int doHashCode() { + if (bigVal != null) { + return bigVal.hashCode(); + } else { + return BigInteger.valueOf(val).hashCode(); + } + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangObject.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangObject.java new file mode 100644 index 0000000000..81220c5685 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangObject.java @@ -0,0 +1,190 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Base class of the Erlang data type classes. This class is used to represent + * an arbitrary Erlang term. + */ +public abstract class OtpErlangObject implements Serializable, Cloneable { + protected int hashCodeValue = 0; + + // don't change this! + static final long serialVersionUID = -8435938572339430044L; + + /** + * @return the printable representation of the object. This is usually + * similar to the representation used by Erlang for the same type of + * object. + */ + @Override + public abstract String toString(); + + /** + * Convert the object according to the rules of the Erlang external format. + * This is mainly used for sending Erlang terms in messages, however it can + * also be used for storing terms to disk. + * + * @param buf + * an output stream to which the encoded term should be + * written. + */ + public abstract void encode(OtpOutputStream buf); + + /** + * Read binary data in the Erlang external format, and produce a + * corresponding Erlang data type object. This method is normally used when + * Erlang terms are received in messages, however it can also be used for + * reading terms from disk. + * + * @param buf + * an input stream containing one or more encoded Erlang + * terms. + * + * @return an object representing one of the Erlang data types. + * + * @exception OtpErlangDecodeException + * if the stream does not contain a valid representation + * of an Erlang term. + */ + public static OtpErlangObject decode(final OtpInputStream buf) + throws OtpErlangDecodeException { + return buf.read_any(); + } + + /** + * Determine if two Erlang objects are equal. In general, Erlang objects are + * equal if the components they consist of are equal. + * + * @param o + * the object to compare to. + * + * @return true if the objects are identical. + */ + @Override + public abstract boolean equals(Object o); + + @Override + public int hashCode() { + if (hashCodeValue == 0) { + hashCodeValue = doHashCode(); + } + return hashCodeValue; + } + + protected int doHashCode() { + return super.hashCode(); + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (final CloneNotSupportedException e) { + /* cannot happen */ + throw new InternalError(e.toString()); + } + } + + protected final static class Hash { + int abc[] = {0, 0, 0}; + + /* Hash function suggested by Bob Jenkins. + * The same as in the Erlang VM (beam); utils.c. + */ + + private final static int HASH_CONST[] = { + 0, // not used + 0x9e3779b9, // the golden ratio; an arbitrary value + 0x3c6ef372, // (hashHConst[1] * 2) % (1<<32) + 0xdaa66d2b, // 1 3 + 0x78dde6e4, // 1 4 + 0x1715609d, // 1 5 + 0xb54cda56, // 1 6 + 0x5384540f, // 1 7 + 0xf1bbcdc8, // 1 8 + 0x8ff34781, // 1 9 + 0x2e2ac13a, // 1 10 + 0xcc623af3, // 1 11 + 0x6a99b4ac, // 1 12 + 0x08d12e65, // 1 13 + 0xa708a81e, // 1 14 + 0x454021d7, // 1 15 + }; + + protected Hash(int i) { + abc[0] = abc[1] = HASH_CONST[i]; + abc[2] = 0; + } + + //protected Hash() { + // Hash(1); + //} + + private void mix() { + abc[0] -= abc[1]; abc[0] -= abc[2]; abc[0] ^= (abc[2]>>>13); + abc[1] -= abc[2]; abc[1] -= abc[0]; abc[1] ^= (abc[0]<<8); + abc[2] -= abc[0]; abc[2] -= abc[1]; abc[2] ^= (abc[1]>>>13); + abc[0] -= abc[1]; abc[0] -= abc[2]; abc[0] ^= (abc[2]>>>12); + abc[1] -= abc[2]; abc[1] -= abc[0]; abc[1] ^= (abc[0]<<16); + abc[2] -= abc[0]; abc[2] -= abc[1]; abc[2] ^= (abc[1]>>>5); + abc[0] -= abc[1]; abc[0] -= abc[2]; abc[0] ^= (abc[2]>>>3); + abc[1] -= abc[2]; abc[1] -= abc[0]; abc[1] ^= (abc[0]<<10); + abc[2] -= abc[0]; abc[2] -= abc[1]; abc[2] ^= (abc[1]>>>15); + } + + protected void combine(int a) { + abc[0] += a; + mix(); + } + + protected void combine(long a) { + combine((int)(a >>> 32), (int) a); + } + + protected void combine(int a, int b) { + abc[0] += a; + abc[1] += b; + mix(); + } + + protected void combine(byte b[]) { + int j, k; + for (j = 0, k = 0; + j + 4 < b.length; + j += 4, k += 1, k %= 3) { + abc[k] += ((int)b[j+0] & 0xFF) + ((int)b[j+1]<<8 & 0xFF00) + + ((int)b[j+2]<<16 & 0xFF0000) + ((int)b[j+3]<<24); + mix(); + } + for (int n = 0, m = 0xFF; + j < b.length; + j++, n += 8, m <<= 8) { + abc[k] += (int)b[j]<<n & m; + } + mix(); + } + + protected int valueOf() { + return abc[2]; + } + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPid.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPid.java new file mode 100644 index 0000000000..fe81ce302d --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPid.java @@ -0,0 +1,210 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang PIDs. PIDs represent Erlang + * processes and consist of a nodename and a number of integers. + */ +public class OtpErlangPid extends OtpErlangObject implements Serializable, + Cloneable, Comparable<Object> { + // don't change this! + static final long serialVersionUID = 1664394142301803659L; + + private final String node; + private final int id; + private final int serial; + private final int creation; + + /** + * Create a unique Erlang PID belonging to the local node. + * + * @param self + * the local node. + * + * @deprecated use OtpLocalNode:createPid() instead + */ + @Deprecated + public OtpErlangPid(final OtpLocalNode self) { + final OtpErlangPid p = self.createPid(); + + id = p.id; + serial = p.serial; + creation = p.creation; + node = p.node; + } + + /** + * Create an Erlang PID from a stream containing a PID encoded in Erlang + * external format. + * + * @param buf + * the stream containing the encoded PID. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang PID. + */ + public OtpErlangPid(final OtpInputStream buf) + throws OtpErlangDecodeException { + final OtpErlangPid p = buf.read_pid(); + + node = p.node(); + id = p.id(); + serial = p.serial(); + creation = p.creation(); + } + + /** + * Create an Erlang pid from its components. + * + * @param node + * the nodename. + * + * @param id + * an arbitrary number. Only the low order 15 bits will be + * used. + * + * @param serial + * another arbitrary number. Only the low order 13 bits will + * be used. + * + * @param creation + * yet another arbitrary number. Only the low order 2 bits + * will be used. + */ + public OtpErlangPid(final String node, final int id, final int serial, + final int creation) { + this.node = node; + this.id = id & 0x7fff; // 15 bits + this.serial = serial & 0x1fff; // 13 bits + this.creation = creation & 0x03; // 2 bits + } + + /** + * Get the serial number from the PID. + * + * @return the serial number from the PID. + */ + public int serial() { + return serial; + } + + /** + * Get the id number from the PID. + * + * @return the id number from the PID. + */ + public int id() { + return id; + } + + /** + * Get the creation number from the PID. + * + * @return the creation number from the PID. + */ + public int creation() { + return creation; + } + + /** + * Get the node name from the PID. + * + * @return the node name from the PID. + */ + public String node() { + return node; + } + + /** + * Get the string representation of the PID. Erlang PIDs are printed as + * #Pid<node.id.serial> + * + * @return the string representation of the PID. + */ + @Override + public String toString() { + return "#Pid<" + node.toString() + "." + id + "." + serial + ">"; + } + + /** + * Convert this PID to the equivalent Erlang external representation. + * + * @param buf + * an output stream to which the encoded PID should be + * written. + */ + @Override + public void encode(final OtpOutputStream buf) { + buf.write_pid(node, id, serial, creation); + } + + /** + * Determine if two PIDs are equal. PIDs are equal if their components are + * equal. + * + * @param port + * the other PID to compare to. + * + * @return true if the PIDs are equal, false otherwise. + */ + @Override + public boolean equals(final Object o) { + if (!(o instanceof OtpErlangPid)) { + return false; + } + + final OtpErlangPid pid = (OtpErlangPid) o; + + return creation == pid.creation && serial == pid.serial && id == pid.id + && node.compareTo(pid.node) == 0; + } + + @Override + protected int doHashCode() { + OtpErlangObject.Hash hash = new OtpErlangObject.Hash(5); + hash.combine(creation, serial); + hash.combine(id, node.hashCode()); + return hash.valueOf(); + } + + public int compareTo(final Object o) { + if (!(o instanceof OtpErlangPid)) { + return -1; + } + + final OtpErlangPid pid = (OtpErlangPid) o; + if (creation == pid.creation) { + if (serial == pid.serial) { + if (id == pid.id) { + return node.compareTo(pid.node); + } else { + return id - pid.id; + } + } else { + return serial - pid.serial; + } + } else { + return creation - pid.creation; + } + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPort.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPort.java new file mode 100644 index 0000000000..2a0eab0a9c --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangPort.java @@ -0,0 +1,169 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang ports. + */ +public class OtpErlangPort extends OtpErlangObject implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = 4037115468007644704L; + + private final String node; + private final int id; + private final int creation; + + /* + * Create a unique Erlang port belonging to the local node. Since it isn't + * meaninful to do so, this constructor is private... + * + * @param self the local node. + * + * @deprecated use OtpLocalNode:createPort() instead + */ + private OtpErlangPort(final OtpSelf self) { + final OtpErlangPort p = self.createPort(); + + id = p.id; + creation = p.creation; + node = p.node; + } + + /** + * Create an Erlang port from a stream containing a port encoded in Erlang + * external format. + * + * @param buf + * the stream containing the encoded port. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang port. + */ + public OtpErlangPort(final OtpInputStream buf) + throws OtpErlangDecodeException { + final OtpErlangPort p = buf.read_port(); + + node = p.node(); + id = p.id(); + creation = p.creation(); + } + + /** + * Create an Erlang port from its components. + * + * @param node + * the nodename. + * + * @param id + * an arbitrary number. Only the low order 28 bits will be + * used. + * + * @param creation + * another arbitrary number. Only the low order 2 bits will + * be used. + */ + public OtpErlangPort(final String node, final int id, final int creation) { + this.node = node; + this.id = id & 0xfffffff; // 28 bits + this.creation = creation & 0x03; // 2 bits + } + + /** + * Get the id number from the port. + * + * @return the id number from the port. + */ + public int id() { + return id; + } + + /** + * Get the creation number from the port. + * + * @return the creation number from the port. + */ + public int creation() { + return creation; + } + + /** + * Get the node name from the port. + * + * @return the node name from the port. + */ + public String node() { + return node; + } + + /** + * Get the string representation of the port. Erlang ports are printed as + * #Port<node.id>. + * + * @return the string representation of the port. + */ + @Override + public String toString() { + return "#Port<" + node + "." + id + ">"; + } + + /** + * Convert this port to the equivalent Erlang external representation. + * + * @param buf + * an output stream to which the encoded port should be + * written. + */ + @Override + public void encode(final OtpOutputStream buf) { + buf.write_port(node, id, creation); + } + + /** + * Determine if two ports are equal. Ports are equal if their components are + * equal. + * + * @param o + * the other port to compare to. + * + * @return true if the ports are equal, false otherwise. + */ + @Override + public boolean equals(final Object o) { + if (!(o instanceof OtpErlangPort)) { + return false; + } + + final OtpErlangPort port = (OtpErlangPort) o; + + return creation == port.creation && id == port.id + && node.compareTo(port.node) == 0; + } + + @Override + protected int doHashCode() { + OtpErlangObject.Hash hash = new OtpErlangObject.Hash(6); + hash.combine(creation); + hash.combine(id, node.hashCode()); + return hash.valueOf(); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangRangeException.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangRangeException.java new file mode 100644 index 0000000000..a78b6df6ef --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangRangeException.java @@ -0,0 +1,42 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +/** + * Exception raised when an attempt is made to create an Erlang term with data + * that is out of range for the term in question. + * + * @see OtpErlangByte + * @see OtpErlangChar + * @see OtpErlangInt + * @see OtpErlangUInt + * @see OtpErlangShort + * @see OtpErlangUShort + * @see OtpErlangLong + */ +public class OtpErlangRangeException extends OtpErlangException { + private static final long serialVersionUID = 1L; + + /** + * Provides a detailed message. + */ + public OtpErlangRangeException(final String msg) { + super(msg); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangRef.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangRef.java new file mode 100644 index 0000000000..8056439962 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangRef.java @@ -0,0 +1,264 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang refs. There are two styles of Erlang + * refs, old style (one id value) and new style (array of id values). This class + * manages both types. + */ +public class OtpErlangRef extends OtpErlangObject implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = -7022666480768586521L; + + private final String node; + private final int creation; + + // old style refs have one 18-bit id + // r6 "new" refs have array of ids, first one is only 18 bits however + private int ids[] = null; + + /** + * Create a unique Erlang ref belonging to the local node. + * + * @param self + * the local node. + * + * @deprecated use OtpLocalNode:createRef() instead + */ + @Deprecated + public OtpErlangRef(final OtpLocalNode self) { + final OtpErlangRef r = self.createRef(); + + ids = r.ids; + creation = r.creation; + node = r.node; + } + + /** + * Create an Erlang ref from a stream containing a ref encoded in Erlang + * external format. + * + * @param buf + * the stream containing the encoded ref. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang ref. + */ + public OtpErlangRef(final OtpInputStream buf) + throws OtpErlangDecodeException { + final OtpErlangRef r = buf.read_ref(); + + node = r.node(); + creation = r.creation(); + + ids = r.ids(); + } + + /** + * Create an old style Erlang ref from its components. + * + * @param node + * the nodename. + * + * @param id + * an arbitrary number. Only the low order 18 bits will be + * used. + * + * @param creation + * another arbitrary number. Only the low order 2 bits will + * be used. + */ + public OtpErlangRef(final String node, final int id, final int creation) { + this.node = node; + ids = new int[1]; + ids[0] = id & 0x3ffff; // 18 bits + this.creation = creation & 0x03; // 2 bits + } + + /** + * Create a new style Erlang ref from its components. + * + * @param node + * the nodename. + * + * @param ids + * an array of arbitrary numbers. Only the low order 18 bits + * of the first number will be used. If the array contains + * only one number, an old style ref will be written instead. + * At most three numbers will be read from the array. + * + * @param creation + * another arbitrary number. Only the low order 2 bits will + * be used. + */ + public OtpErlangRef(final String node, final int[] ids, final int creation) { + this.node = node; + this.creation = creation & 0x03; // 2 bits + + // use at most 82 bits (18 + 32 + 32) + int len = ids.length; + this.ids = new int[3]; + this.ids[0] = 0; + this.ids[1] = 0; + this.ids[2] = 0; + + if (len > 3) { + len = 3; + } + System.arraycopy(ids, 0, this.ids, 0, len); + this.ids[0] &= 0x3ffff; // only 18 significant bits in first number + } + + /** + * Get the id number from the ref. Old style refs have only one id number. + * If this is a new style ref, the first id number is returned. + * + * @return the id number from the ref. + */ + public int id() { + return ids[0]; + } + + /** + * Get the array of id numbers from the ref. If this is an old style ref, + * the array is of length 1. If this is a new style ref, the array has + * length 3. + * + * @return the array of id numbers from the ref. + */ + public int[] ids() { + return ids; + } + + /** + * Determine whether this is a new style ref. + * + * @return true if this ref is a new style ref, false otherwise. + */ + public boolean isNewRef() { + return ids.length > 1; + } + + /** + * Get the creation number from the ref. + * + * @return the creation number from the ref. + */ + public int creation() { + return creation; + } + + /** + * Get the node name from the ref. + * + * @return the node name from the ref. + */ + public String node() { + return node; + } + + /** + * Get the string representation of the ref. Erlang refs are printed as + * #Ref<node.id> + * + * @return the string representation of the ref. + */ + @Override + public String toString() { + String s = "#Ref<" + node; + + for (int i = 0; i < ids.length; i++) { + s += "." + ids[i]; + } + + s += ">"; + + return s; + } + + /** + * Convert this ref to the equivalent Erlang external representation. + * + * @param buf + * an output stream to which the encoded ref should be + * written. + */ + @Override + public void encode(final OtpOutputStream buf) { + buf.write_ref(node, ids, creation); + } + + /** + * Determine if two refs are equal. Refs are equal if their components are + * equal. New refs and old refs are considered equal if the node, creation + * and first id numnber are equal. + * + * @param o + * the other ref to compare to. + * + * @return true if the refs are equal, false otherwise. + */ + @Override + public boolean equals(final Object o) { + if (!(o instanceof OtpErlangRef)) { + return false; + } + + final OtpErlangRef ref = (OtpErlangRef) o; + + if (!(node.equals(ref.node()) && creation == ref.creation())) { + return false; + } + + if (isNewRef() && ref.isNewRef()) { + return ids[0] == ref.ids[0] && ids[1] == ref.ids[1] + && ids[2] == ref.ids[2]; + } + return ids[0] == ref.ids[0]; + } + + /** + * Compute the hashCode value for a given ref. This function is compatible + * with equal. + * + * @return the hashCode of the node. + **/ + + @Override + protected int doHashCode() { + OtpErlangObject.Hash hash = new OtpErlangObject.Hash(7); + hash.combine(creation, ids[0]); + if (isNewRef()) { + hash.combine(ids[1], ids[2]); + } + return hash.valueOf(); + } + + @Override + public Object clone() { + final OtpErlangRef newRef = (OtpErlangRef) super.clone(); + newRef.ids = ids.clone(); + return newRef; + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangShort.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangShort.java new file mode 100644 index 0000000000..cd232570dd --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangShort.java @@ -0,0 +1,63 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang integral types. + */ +public class OtpErlangShort extends OtpErlangLong implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = 7162345156603088099L; + + /** + * Create an Erlang integer from the given value. + * + * @param s + * the short value to use. + */ + public OtpErlangShort(final short s) { + super(s); + } + + /** + * Create an Erlang integer from a stream containing an integer encoded in + * Erlang external format. + * + * @param buf + * the stream containing the encoded value. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang integer. + * + * @exception OtpErlangRangeException + * if the value is too large to be represented as a + * short. + */ + public OtpErlangShort(final OtpInputStream buf) + throws OtpErlangRangeException, OtpErlangDecodeException { + super(buf); + + final short j = shortValue(); + } + +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangString.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangString.java new file mode 100644 index 0000000000..19ee92e0d0 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangString.java @@ -0,0 +1,198 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; +import java.lang.Character; +import java.io.UnsupportedEncodingException; + +/** + * Provides a Java representation of Erlang strings. + */ +public class OtpErlangString extends OtpErlangObject implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = -7053595217604929233L; + + private final String str; + + /** + * Create an Erlang string from the given string. + */ + public OtpErlangString(final String str) { + this.str = str; + } + + /** + * Create an Erlang string from a list of integers. + * + * @return an Erlang string with Unicode code units. + * + * @throws OtpErlangException + * for non-proper and non-integer lists. + * @throws OtpErlangRangeException + * if an integer in the list is not + * a valid Unicode code point according to Erlang. + */ + public OtpErlangString(final OtpErlangList list) + throws OtpErlangException { + String s = list.stringValue(); + final int n = s.length(); + for (int i = 0; i < n; i = s.offsetByCodePoints(i, 1)) { + int cp = s.codePointAt(i); + if (! isValidCodePoint(cp)) { + throw new OtpErlangRangeException("Invalid CodePoint: " + cp); + } + } + str = s; + } + + /** + * Create an Erlang string from a stream containing a string encoded in + * Erlang external format. + * + * @param buf + * the stream containing the encoded string. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang string. + */ + public OtpErlangString(final OtpInputStream buf) + throws OtpErlangDecodeException { + str = buf.read_string(); + } + + /** + * Get the actual string contained in this object. + * + * @return the raw string contained in this object, without regard to Erlang + * quoting rules. + * + * @see #toString + */ + public String stringValue() { + return str; + } + + /** + * Get the printable version of the string contained in this object. + * + * @return the string contained in this object, quoted. + * + * @see #stringValue + */ + + @Override + public String toString() { + return "\"" + str + "\""; + } + + /** + * Convert this string to the equivalent Erlang external representation. + * + * @param buf + * an output stream to which the encoded string should be + * written. + */ + + @Override + public void encode(final OtpOutputStream buf) { + buf.write_string(str); + } + + /** + * Determine if two strings are equal. They are equal if they represent the + * same sequence of characters. This method can be used to compare + * OtpErlangStrings with each other and with Strings. + * + * @param o + * the OtpErlangString or String to compare to. + * + * @return true if the strings consist of the same sequence of characters, + * false otherwise. + */ + + @Override + public boolean equals(final Object o) { + if (o instanceof String) { + return str.compareTo((String) o) == 0; + } else if (o instanceof OtpErlangString) { + return str.compareTo(((OtpErlangString) o).str) == 0; + } + + return false; + } + + protected int doHashCode() { + return str.hashCode(); + } + + /** + * Create Unicode code points from a String. + * + * @param s + * a String to convert to an Unicode code point array + * + * @return the corresponding array of integers representing + * Unicode code points + */ + + public static int[] stringToCodePoints(final String s) { + final int m = s.codePointCount(0, s.length()); + final int [] codePoints = new int[m]; + for (int i = 0, j = 0; j < m; i = s.offsetByCodePoints(i, 1), j++) { + codePoints[j] = s.codePointAt(i); + } + return codePoints; + } + + /** + * Validate a code point according to Erlang definition; Unicode 3.0. + * That is; valid in the range U+0..U+10FFFF, but not in the range + * U+D800..U+DFFF (surrogat pairs), nor U+FFFE..U+FFFF (non-characters). + * + * @param cp + * the code point value to validate + * + * @return true if the code point is valid, + * false otherwise. + */ + + public static boolean isValidCodePoint(final int cp) { + // Erlang definition of valid Unicode code points; + // Unicode 3.0, XML, et.al. + return (cp>>>16) <= 0x10 // in 0..10FFFF; Unicode range + && (cp & ~0x7FF) != 0xD800 // not in D800..DFFF; surrogate range + && (cp & ~1) != 0xFFFE; // not in FFFE..FFFF; non-characters + } + + /** + * Construct a String from a Latin-1 (ISO-8859-1) encoded byte array, + * if Latin-1 is available, otherwise use the default encoding. + * + */ + public static String newString(final byte[] bytes) { + try { + return new String(bytes, "ISO-8859-1"); + } catch (final UnsupportedEncodingException e) { + } + return new String(bytes); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangTuple.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangTuple.java new file mode 100644 index 0000000000..b3a1021992 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangTuple.java @@ -0,0 +1,261 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang tuples. Tuples are created from one + * or more arbitrary Erlang terms. + * + * <p> + * The arity of the tuple is the number of elements it contains. Elements are + * indexed from 0 to (arity-1) and can be retrieved individually by using the + * appropriate index. + */ +public class OtpErlangTuple extends OtpErlangObject implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = 9163498658004915935L; + + private static final OtpErlangObject[] NO_ELEMENTS = new OtpErlangObject[0]; + + private OtpErlangObject[] elems = NO_ELEMENTS; + + /** + * Create a unary tuple containing the given element. + * + * @param elem + * the element to create the tuple from. + * + * @exception java.lang.IllegalArgumentException + * if the element is null. + */ + public OtpErlangTuple(final OtpErlangObject elem) { + if (elem == null) { + throw new java.lang.IllegalArgumentException( + "Tuple element cannot be null"); + } else { + elems = new OtpErlangObject[] { elem }; + } + } + + /** + * Create a tuple from an array of terms. + * + * @param elems + * the array of terms to create the tuple from. + * + * @exception java.lang.IllegalArgumentException + * if the array is empty (null) or contains null + * elements. + */ + public OtpErlangTuple(final OtpErlangObject[] elems) { + this(elems, 0, elems.length); + } + + /** + * Create a tuple from an array of terms. + * + * @param elems + * the array of terms to create the tuple from. + * @param start + * the offset of the first term to insert. + * @param count + * the number of terms to insert. + * + * @exception java.lang.IllegalArgumentException + * if the array is empty (null) or contains null + * elements. + */ + public OtpErlangTuple(OtpErlangObject[] elems, final int start, + final int count) { + if (elems == null) { + throw new java.lang.IllegalArgumentException( + "Tuple content can't be null"); + } else if (count < 1) { + elems = NO_ELEMENTS; + } else { + this.elems = new OtpErlangObject[count]; + for (int i = 0; i < count; i++) { + if (elems[start + i] != null) { + this.elems[i] = elems[start + i]; + } else { + throw new java.lang.IllegalArgumentException( + "Tuple element cannot be null (element" + + (start + i) + ")"); + } + } + } + } + + /** + * Create a tuple from a stream containing an tuple encoded in Erlang + * external format. + * + * @param buf + * the stream containing the encoded tuple. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang tuple. + */ + public OtpErlangTuple(final OtpInputStream buf) + throws OtpErlangDecodeException { + final int arity = buf.read_tuple_head(); + + if (arity > 0) { + elems = new OtpErlangObject[arity]; + + for (int i = 0; i < arity; i++) { + elems[i] = buf.read_any(); + } + } else { + elems = NO_ELEMENTS; + } + } + + /** + * Get the arity of the tuple. + * + * @return the number of elements contained in the tuple. + */ + public int arity() { + return elems.length; + } + + /** + * Get the specified element from the tuple. + * + * @param i + * the index of the requested element. Tuple elements are + * numbered as array elements, starting at 0. + * + * @return the requested element, of null if i is not a valid element index. + */ + public OtpErlangObject elementAt(final int i) { + if (i >= arity() || i < 0) { + return null; + } + return elems[i]; + } + + /** + * Get all the elements from the tuple as an array. + * + * @return an array containing all of the tuple's elements. + */ + public OtpErlangObject[] elements() { + final OtpErlangObject[] res = new OtpErlangObject[arity()]; + System.arraycopy(elems, 0, res, 0, res.length); + return res; + } + + /** + * Get the string representation of the tuple. + * + * @return the string representation of the tuple. + */ + @Override + public String toString() { + int i; + final StringBuffer s = new StringBuffer(); + final int arity = elems.length; + + s.append("{"); + + for (i = 0; i < arity; i++) { + if (i > 0) { + s.append(","); + } + s.append(elems[i].toString()); + } + + s.append("}"); + + return s.toString(); + } + + /** + * Convert this tuple to the equivalent Erlang external representation. + * + * @param buf + * an output stream to which the encoded tuple should be + * written. + */ + @Override + public void encode(final OtpOutputStream buf) { + final int arity = elems.length; + + buf.write_tuple_head(arity); + + for (int i = 0; i < arity; i++) { + buf.write_any(elems[i]); + } + } + + /** + * Determine if two tuples are equal. Tuples are equal if they have the same + * arity and all of the elements are equal. + * + * @param o + * the tuple to compare to. + * + * @return true if the tuples have the same arity and all the elements are + * equal. + */ + @Override + public boolean equals(final Object o) { + if (!(o instanceof OtpErlangTuple)) { + return false; + } + + final OtpErlangTuple t = (OtpErlangTuple) o; + final int a = arity(); + + if (a != t.arity()) { + return false; + } + + for (int i = 0; i < a; i++) { + if (!elems[i].equals(t.elems[i])) { + return false; // early exit + } + } + + return true; + } + + protected int doHashCode() { + OtpErlangObject.Hash hash = new OtpErlangObject.Hash(9); + final int a = arity(); + hash.combine(a); + for (int i = 0; i < a; i++) { + hash.combine(elems[i].hashCode()); + } + return hash.valueOf(); + } + + @Override + public Object clone() { + final OtpErlangTuple newTuple = (OtpErlangTuple) super.clone(); + newTuple.elems = elems.clone(); + return newTuple; + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangUInt.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangUInt.java new file mode 100644 index 0000000000..f01354d821 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangUInt.java @@ -0,0 +1,67 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang integral types. + */ +public class OtpErlangUInt extends OtpErlangLong implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = -1450956122937471885L; + + /** + * Create an Erlang integer from the given value. + * + * @param i + * the non-negative int value to use. + * + * @exception OtpErlangRangeException + * if the value is negative. + */ + public OtpErlangUInt(final int i) throws OtpErlangRangeException { + super(i); + + final int j = uIntValue(); + } + + /** + * Create an Erlang integer from a stream containing an integer encoded in + * Erlang external format. + * + * @param buf + * the stream containing the encoded value. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang integer. + * + * @exception OtpErlangRangeException + * if the value is too large to be represented as an int, + * or the value is negative. + */ + public OtpErlangUInt(final OtpInputStream buf) + throws OtpErlangRangeException, OtpErlangDecodeException { + super(buf); + + final int j = uIntValue(); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangUShort.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangUShort.java new file mode 100644 index 0000000000..6b6bc7a56b --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpErlangUShort.java @@ -0,0 +1,67 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.Serializable; + +/** + * Provides a Java representation of Erlang integral types. + */ +public class OtpErlangUShort extends OtpErlangLong implements Serializable, + Cloneable { + // don't change this! + static final long serialVersionUID = 300370950578307246L; + + /** + * Create an Erlang integer from the given value. + * + * @param s + * the non-negative short value to use. + * + * @exception OtpErlangRangeException + * if the value is negative. + */ + public OtpErlangUShort(final short s) throws OtpErlangRangeException { + super(s); + + final short j = uShortValue(); + } + + /** + * Create an Erlang integer from a stream containing an integer encoded in + * Erlang external format. + * + * @param buf + * the stream containing the encoded value. + * + * @exception OtpErlangDecodeException + * if the buffer does not contain a valid external + * representation of an Erlang integer. + * + * @exception OtpErlangRangeException + * if the value is too large to be represented as a + * short, or the value is negative. + */ + public OtpErlangUShort(final OtpInputStream buf) + throws OtpErlangRangeException, OtpErlangDecodeException { + super(buf); + + final short j = uShortValue(); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpException.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpException.java new file mode 100644 index 0000000000..33d25b6021 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpException.java @@ -0,0 +1,38 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +/** + * Base class for the other OTP exception classes. + */ +public abstract class OtpException extends Exception { + /** + * Provides no message. + */ + public OtpException() { + super(); + } + + /** + * Provides a detailed message. + */ + public OtpException(final String msg) { + super(msg); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpExternal.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpExternal.java new file mode 100644 index 0000000000..e70b9a786b --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpExternal.java @@ -0,0 +1,105 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +/** + * Provides a collection of constants used when encoding and decoding Erlang + * terms. + */ +public class OtpExternal { + // no constructor + private OtpExternal() { + } + + /** The tag used for small integers */ + public static final int smallIntTag = 97; + + /** The tag used for integers */ + public static final int intTag = 98; + + /** The tag used for floating point numbers */ + public static final int floatTag = 99; + public static final int newFloatTag = 70; + + /** The tag used for atoms */ + public static final int atomTag = 100; + + /** The tag used for old stype references */ + public static final int refTag = 101; + + /** The tag used for ports */ + public static final int portTag = 102; + + /** The tag used for PIDs */ + public static final int pidTag = 103; + + /** The tag used for small tuples */ + public static final int smallTupleTag = 104; + + /** The tag used for large tuples */ + public static final int largeTupleTag = 105; + + /** The tag used for empty lists */ + public static final int nilTag = 106; + + /** The tag used for strings and lists of small integers */ + public static final int stringTag = 107; + + /** The tag used for non-empty lists */ + public static final int listTag = 108; + + /** The tag used for binaries */ + public static final int binTag = 109; + + /** The tag used for bitstrs */ + public static final int bitBinTag = 77; + + /** The tag used for small bignums */ + public static final int smallBigTag = 110; + + /** The tag used for large bignums */ + public static final int largeBigTag = 111; + + /** The tag used for old new Funs */ + public static final int newFunTag = 112; + + /** The tag used for external Funs (M:F/A) */ + public static final int externalFunTag = 113; + + /** The tag used for new style references */ + public static final int newRefTag = 114; + + /** The tag used for old Funs */ + public static final int funTag = 117; + + /** The tag used for compressed terms */ + public static final int compressedTag = 80; + + /** The version number used to mark serialized Erlang terms */ + public static final int versionTag = 131; + + /** The largest value that can be encoded as an integer */ + public static final int erlMax = (1 << 27) - 1; + + /** The smallest value that can be encoded as an integer */ + public static final int erlMin = -(1 << 27); + + /** The longest allowed Erlang atom */ + public static final int maxAtomLength = 255; +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java new file mode 100644 index 0000000000..b9b43481ee --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpInputStream.java @@ -0,0 +1,1203 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.math.BigDecimal; + +/** + * Provides a stream for decoding Erlang terms from external format. + * + * <p> + * Note that this class is not synchronized, if you need synchronization you + * must provide it yourself. + */ +public class OtpInputStream extends ByteArrayInputStream { + + public static int DECODE_INT_LISTS_AS_STRINGS = 1; + + private final int flags; + + /** + * @param buf + */ + public OtpInputStream(final byte[] buf) { + this(buf, 0); + } + + /** + * Create a stream from a buffer containing encoded Erlang terms. + * + * @param flags + */ + public OtpInputStream(final byte[] buf, final int flags) { + super(buf); + this.flags = flags; + } + + /** + * Create a stream from a buffer containing encoded Erlang terms at the + * given offset and length. + * + * @param flags + */ + public OtpInputStream(final byte[] buf, final int offset, final int length, + final int flags) { + super(buf, offset, length); + this.flags = flags; + } + + /** + * Get the current position in the stream. + * + * @return the current position in the stream. + */ + public int getPos() { + return super.pos; + } + + /** + * Set the current position in the stream. + * + * @param pos + * the position to move to in the stream. If pos indicates a + * position beyond the end of the stream, the position is move to + * the end of the stream instead. If pos is negative, the + * position is moved to the beginning of the stream instead. + * + * @return the previous position in the stream. + */ + public int setPos(int pos) { + final int oldpos = super.pos; + + if (pos > super.count) { + pos = super.count; + } else if (pos < 0) { + pos = 0; + } + + super.pos = pos; + + return oldpos; + } + + /** + * Read an array of bytes from the stream. The method reads at most + * buf.length bytes from the input stream. + * + * @return the number of bytes read. + * + * @exception OtpErlangDecodeException + * if the next byte cannot be read. + */ + public int readN(final byte[] buf) throws OtpErlangDecodeException { + return this.readN(buf, 0, buf.length); + } + + /** + * Read an array of bytes from the stream. The method reads at most len + * bytes from the input stream into offset off of the buffer. + * + * @return the number of bytes read. + * + * @exception OtpErlangDecodeException + * if the next byte cannot be read. + */ + public int readN(final byte[] buf, final int off, final int len) + throws OtpErlangDecodeException { + if (len == 0 && available() == 0) { + return 0; + } + final int i = super.read(buf, off, len); + if (i < 0) { + throw new OtpErlangDecodeException("Cannot read from input stream"); + } + return i; + } + + /** + * Alias for peek1() + */ + public int peek() throws OtpErlangDecodeException { + return peek1(); + } + + /** + * Look ahead one position in the stream without consuming the byte found + * there. + * + * @return the next byte in the stream, as an integer. + * + * @exception OtpErlangDecodeException + * if the next byte cannot be read. + */ + public int peek1() throws OtpErlangDecodeException { + int i; + try { + i = super.buf[super.pos]; + if (i < 0) { + i += 256; + } + + return i; + } catch (final Exception e) { + throw new OtpErlangDecodeException("Cannot read from input stream"); + } + } + + public int peek1skip_version() throws OtpErlangDecodeException { + int i = peek1(); + if (i == OtpExternal.versionTag) { + read1(); + i = peek1(); + } + return i; + } + + /** + * Read a one byte integer from the stream. + * + * @return the byte read, as an integer. + * + * @exception OtpErlangDecodeException + * if the next byte cannot be read. + */ + public int read1() throws OtpErlangDecodeException { + int i; + i = super.read(); + + if (i < 0) { + throw new OtpErlangDecodeException("Cannot read from input stream"); + } + + return i; + } + + public int read1skip_version() throws OtpErlangDecodeException { + int tag = read1(); + if (tag == OtpExternal.versionTag) { + tag = read1(); + } + return tag; + } + + /** + * Read a two byte big endian integer from the stream. + * + * @return the bytes read, converted from big endian to an integer. + * + * @exception OtpErlangDecodeException + * if the next byte cannot be read. + */ + public int read2BE() throws OtpErlangDecodeException { + final byte[] b = new byte[2]; + try { + super.read(b); + } catch (final IOException e) { + throw new OtpErlangDecodeException("Cannot read from input stream"); + } + ; + return (b[0] << 8 & 0xff00) + (b[1] & 0xff); + } + + /** + * Read a four byte big endian integer from the stream. + * + * @return the bytes read, converted from big endian to an integer. + * + * @exception OtpErlangDecodeException + * if the next byte cannot be read. + */ + public int read4BE() throws OtpErlangDecodeException { + final byte[] b = new byte[4]; + try { + super.read(b); + } catch (final IOException e) { + throw new OtpErlangDecodeException("Cannot read from input stream"); + } + ; + return (b[0] << 24 & 0xff000000) + (b[1] << 16 & 0xff0000) + + (b[2] << 8 & 0xff00) + (b[3] & 0xff); + } + + /** + * Read a two byte little endian integer from the stream. + * + * @return the bytes read, converted from little endian to an integer. + * + * @exception OtpErlangDecodeException + * if the next byte cannot be read. + */ + public int read2LE() throws OtpErlangDecodeException { + final byte[] b = new byte[2]; + try { + super.read(b); + } catch (final IOException e) { + throw new OtpErlangDecodeException("Cannot read from input stream"); + } + ; + return (b[1] << 8 & 0xff00) + (b[0] & 0xff); + } + + /** + * Read a four byte little endian integer from the stream. + * + * @return the bytes read, converted from little endian to an integer. + * + * @exception OtpErlangDecodeException + * if the next byte cannot be read. + */ + public int read4LE() throws OtpErlangDecodeException { + final byte[] b = new byte[4]; + try { + super.read(b); + } catch (final IOException e) { + throw new OtpErlangDecodeException("Cannot read from input stream"); + } + ; + return (b[3] << 24 & 0xff000000) + (b[2] << 16 & 0xff0000) + + (b[1] << 8 & 0xff00) + (b[0] & 0xff); + } + + /** + * Read a little endian integer from the stream. + * + * @param n + * the number of bytes to read + * + * @return the bytes read, converted from little endian to an integer. + * + * @exception OtpErlangDecodeException + * if the next byte cannot be read. + */ + public long readLE(int n) throws OtpErlangDecodeException { + final byte[] b = new byte[n]; + try { + super.read(b); + } catch (final IOException e) { + throw new OtpErlangDecodeException("Cannot read from input stream"); + } + ; + long v = 0; + while (n-- > 0) { + v = v << 8 | (long) b[n] & 0xff; + } + return v; + } + + /** + * Read a bigendian integer from the stream. + * + * @param n + * the number of bytes to read + * + * @return the bytes read, converted from big endian to an integer. + * + * @exception OtpErlangDecodeException + * if the next byte cannot be read. + */ + public long readBE(final int n) throws OtpErlangDecodeException { + final byte[] b = new byte[n]; + try { + super.read(b); + } catch (final IOException e) { + throw new OtpErlangDecodeException("Cannot read from input stream"); + } + ; + long v = 0; + for (int i = 0; i < n; i++) { + v = v << 8 | (long) b[i] & 0xff; + } + return v; + } + + /** + * Read an Erlang atom from the stream and interpret the value as a boolean. + * + * @return true if the atom at the current position in the stream contains + * the value 'true' (ignoring case), false otherwise. + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not an atom. + */ + public boolean read_boolean() throws OtpErlangDecodeException { + return Boolean.valueOf(read_atom()).booleanValue(); + } + + /** + * Read an Erlang atom from the stream. + * + * @return a String containing the value of the atom. + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not an atom. + */ + public String read_atom() throws OtpErlangDecodeException { + int tag; + int len; + byte[] strbuf; + String atom; + + tag = read1skip_version(); + + if (tag != OtpExternal.atomTag) { + throw new OtpErlangDecodeException( + "wrong tag encountered, expected " + OtpExternal.atomTag + + ", got " + tag); + } + + len = read2BE(); + + strbuf = new byte[len]; + this.readN(strbuf); + atom = OtpErlangString.newString(strbuf); + + if (atom.length() > OtpExternal.maxAtomLength) { + atom = atom.substring(0, OtpExternal.maxAtomLength); + } + + return atom; + } + + /** + * Read an Erlang binary from the stream. + * + * @return a byte array containing the value of the binary. + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not a binary. + */ + public byte[] read_binary() throws OtpErlangDecodeException { + int tag; + int len; + byte[] bin; + + tag = read1skip_version(); + + if (tag != OtpExternal.binTag) { + throw new OtpErlangDecodeException( + "Wrong tag encountered, expected " + OtpExternal.binTag + + ", got " + tag); + } + + len = read4BE(); + + bin = new byte[len]; + this.readN(bin); + + return bin; + } + + /** + * Read an Erlang bitstr from the stream. + * + * @param pad_bits + * an int array whose first element will be set to the number of + * pad bits in the last byte. + * + * @return a byte array containing the value of the bitstr. + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not a bitstr. + */ + public byte[] read_bitstr(final int pad_bits[]) + throws OtpErlangDecodeException { + int tag; + int len; + byte[] bin; + + tag = read1skip_version(); + + if (tag != OtpExternal.bitBinTag) { + throw new OtpErlangDecodeException( + "Wrong tag encountered, expected " + OtpExternal.bitBinTag + + ", got " + tag); + } + + len = read4BE(); + bin = new byte[len]; + final int tail_bits = read1(); + if (tail_bits < 0 || 7 < tail_bits) { + throw new OtpErlangDecodeException( + "Wrong tail bit count in bitstr: " + tail_bits); + } + if (len == 0 && tail_bits != 0) { + throw new OtpErlangDecodeException( + "Length 0 on bitstr with tail bit count: " + tail_bits); + } + this.readN(bin); + + pad_bits[0] = 8 - tail_bits; + return bin; + } + + /** + * Read an Erlang float from the stream. + * + * @return the float value. + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not a float. + */ + public float read_float() throws OtpErlangDecodeException { + final double d = read_double(); + return (float) d; + } + + /** + * Read an Erlang float from the stream. + * + * @return the float value, as a double. + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not a float. + */ + public double read_double() throws OtpErlangDecodeException { + int tag; + + // parse the stream + tag = read1skip_version(); + + switch (tag) { + case OtpExternal.newFloatTag: { + return Double.longBitsToDouble(readBE(8)); + } + case OtpExternal.floatTag: { + BigDecimal val; + int epos; + int exp; + final byte[] strbuf = new byte[31]; + String str; + + // get the string + this.readN(strbuf); + str = OtpErlangString.newString(strbuf); + + // find the exponent prefix 'e' in the string + epos = str.indexOf('e', 0); + + if (epos < 0) { + throw new OtpErlangDecodeException("Invalid float format: '" + + str + "'"); + } + + // remove the sign from the exponent, if positive + String estr = str.substring(epos + 1).trim(); + + if (estr.substring(0, 1).equals("+")) { + estr = estr.substring(1); + } + + // now put the mantissa and exponent together + exp = Integer.valueOf(estr).intValue(); + val = new BigDecimal(str.substring(0, epos)).movePointRight(exp); + + return val.doubleValue(); + } + default: + throw new OtpErlangDecodeException( + "Wrong tag encountered, expected " + + OtpExternal.newFloatTag + ", got " + tag); + } + } + + /** + * Read one byte from the stream. + * + * @return the byte read. + * + * @exception OtpErlangDecodeException + * if the next byte cannot be read. + */ + public byte read_byte() throws OtpErlangDecodeException { + final long l = this.read_long(false); + final byte i = (byte) l; + + if (l != i) { + throw new OtpErlangDecodeException("Value does not fit in byte: " + + l); + } + + return i; + } + + /** + * Read a character from the stream. + * + * @return the character value. + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not an integer that can + * be represented as a char. + */ + public char read_char() throws OtpErlangDecodeException { + final long l = this.read_long(true); + final char i = (char) l; + + if (l != (i & 0xffffL)) { + throw new OtpErlangDecodeException("Value does not fit in char: " + + l); + } + + return i; + } + + /** + * Read an unsigned integer from the stream. + * + * @return the integer value. + * + * @exception OtpErlangDecodeException + * if the next term in the stream can not be represented as a + * positive integer. + */ + public int read_uint() throws OtpErlangDecodeException { + final long l = this.read_long(true); + final int i = (int) l; + + if (l != (i & 0xFFFFffffL)) { + throw new OtpErlangDecodeException("Value does not fit in uint: " + + l); + } + + return i; + } + + /** + * Read an integer from the stream. + * + * @return the integer value. + * + * @exception OtpErlangDecodeException + * if the next term in the stream can not be represented as + * an integer. + */ + public int read_int() throws OtpErlangDecodeException { + final long l = this.read_long(false); + final int i = (int) l; + + if (l != i) { + throw new OtpErlangDecodeException("Value does not fit in int: " + + l); + } + + return i; + } + + /** + * Read an unsigned short from the stream. + * + * @return the short value. + * + * @exception OtpErlangDecodeException + * if the next term in the stream can not be represented as a + * positive short. + */ + public short read_ushort() throws OtpErlangDecodeException { + final long l = this.read_long(true); + final short i = (short) l; + + if (l != (i & 0xffffL)) { + throw new OtpErlangDecodeException("Value does not fit in ushort: " + + l); + } + + return i; + } + + /** + * Read a short from the stream. + * + * @return the short value. + * + * @exception OtpErlangDecodeException + * if the next term in the stream can not be represented as a + * short. + */ + public short read_short() throws OtpErlangDecodeException { + final long l = this.read_long(false); + final short i = (short) l; + + if (l != i) { + throw new OtpErlangDecodeException("Value does not fit in short: " + + l); + } + + return i; + } + + /** + * Read an unsigned long from the stream. + * + * @return the long value. + * + * @exception OtpErlangDecodeException + * if the next term in the stream can not be represented as a + * positive long. + */ + public long read_ulong() throws OtpErlangDecodeException { + return this.read_long(true); + } + + /** + * Read a long from the stream. + * + * @return the long value. + * + * @exception OtpErlangDecodeException + * if the next term in the stream can not be represented as a + * long. + */ + public long read_long() throws OtpErlangDecodeException { + return this.read_long(false); + } + + public long read_long(final boolean unsigned) + throws OtpErlangDecodeException { + final byte[] b = read_integer_byte_array(); + return OtpInputStream.byte_array_to_long(b, unsigned); + } + + /** + * Read an integer from the stream. + * + * @return the value as a big endian 2's complement byte array. + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not an integer. + */ + public byte[] read_integer_byte_array() throws OtpErlangDecodeException { + int tag; + byte[] nb; + + tag = read1skip_version(); + + switch (tag) { + case OtpExternal.smallIntTag: + nb = new byte[2]; + nb[0] = 0; + nb[1] = (byte) read1(); + break; + + case OtpExternal.intTag: + nb = new byte[4]; + if (this.readN(nb) != 4) { // Big endian + throw new OtpErlangDecodeException( + "Cannot read from intput stream"); + } + break; + + case OtpExternal.smallBigTag: + case OtpExternal.largeBigTag: + int arity; + int sign; + if (tag == OtpExternal.smallBigTag) { + arity = read1(); + sign = read1(); + } else { + arity = read4BE(); + sign = read1(); + if (arity + 1 < 0) { + throw new OtpErlangDecodeException( + "Value of largeBig does not fit in BigInteger, arity " + + arity + " sign " + sign); + } + } + nb = new byte[arity + 1]; + // Value is read as little endian. The big end is augumented + // with one zero byte to make the value 2's complement positive. + if (this.readN(nb, 0, arity) != arity) { + throw new OtpErlangDecodeException( + "Cannot read from intput stream"); + } + // Reverse the array to make it big endian. + for (int i = 0, j = nb.length; i < j--; i++) { + // Swap [i] with [j] + final byte b = nb[i]; + nb[i] = nb[j]; + nb[j] = b; + } + if (sign != 0) { + // 2's complement negate the big endian value in the array + int c = 1; // Carry + for (int j = nb.length; j-- > 0;) { + c = (~nb[j] & 0xFF) + c; + nb[j] = (byte) c; + c >>= 8; + } + } + break; + + default: + throw new OtpErlangDecodeException("Not valid integer tag: " + tag); + } + + return nb; + } + + public static long byte_array_to_long(final byte[] b, final boolean unsigned) + throws OtpErlangDecodeException { + long v; + switch (b.length) { + case 0: + v = 0; + break; + case 2: + v = ((b[0] & 0xFF) << 8) + (b[1] & 0xFF); + v = (short) v; // Sign extend + if (v < 0 && unsigned) { + throw new OtpErlangDecodeException("Value not unsigned: " + v); + } + break; + case 4: + v = ((b[0] & 0xFF) << 24) + ((b[1] & 0xFF) << 16) + + ((b[2] & 0xFF) << 8) + (b[3] & 0xFF); + v = (int) v; // Sign extend + if (v < 0 && unsigned) { + throw new OtpErlangDecodeException("Value not unsigned: " + v); + } + break; + default: + int i = 0; + final byte c = b[i]; + // Skip non-essential leading bytes + if (unsigned) { + if (c < 0) { + throw new OtpErlangDecodeException("Value not unsigned: " + + b); + } + while (b[i] == 0) { + i++; // Skip leading zero sign bytes + } + } else { + if (c == 0 || c == -1) { // Leading sign byte + i = 1; + // Skip all leading sign bytes + while (i < b.length && b[i] == c) { + i++; + } + if (i < b.length) { + // Check first non-sign byte to see if its sign + // matches the whole number's sign. If not one more + // byte is needed to represent the value. + if (((c ^ b[i]) & 0x80) != 0) { + i--; + } + } + } + } + if (b.length - i > 8) { + // More than 64 bits of value + throw new OtpErlangDecodeException( + "Value does not fit in long: " + b); + } + // Convert the necessary bytes + for (v = c < 0 ? -1 : 0; i < b.length; i++) { + v = v << 8 | b[i] & 0xFF; + } + } + return v; + } + + /** + * Read a list header from the stream. + * + * @return the arity of the list. + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not a list. + */ + public int read_list_head() throws OtpErlangDecodeException { + int arity = 0; + final int tag = read1skip_version(); + + switch (tag) { + case OtpExternal.nilTag: + arity = 0; + break; + + case OtpExternal.stringTag: + arity = read2BE(); + break; + + case OtpExternal.listTag: + arity = read4BE(); + break; + + default: + throw new OtpErlangDecodeException("Not valid list tag: " + tag); + } + + return arity; + } + + /** + * Read a tuple header from the stream. + * + * @return the arity of the tuple. + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not a tuple. + */ + public int read_tuple_head() throws OtpErlangDecodeException { + int arity = 0; + final int tag = read1skip_version(); + + // decode the tuple header and get arity + switch (tag) { + case OtpExternal.smallTupleTag: + arity = read1(); + break; + + case OtpExternal.largeTupleTag: + arity = read4BE(); + break; + + default: + throw new OtpErlangDecodeException("Not valid tuple tag: " + tag); + } + + return arity; + } + + /** + * Read an empty list from the stream. + * + * @return zero (the arity of the list). + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not an empty list. + */ + public int read_nil() throws OtpErlangDecodeException { + int arity = 0; + final int tag = read1skip_version(); + + switch (tag) { + case OtpExternal.nilTag: + arity = 0; + break; + + default: + throw new OtpErlangDecodeException("Not valid nil tag: " + tag); + } + + return arity; + } + + /** + * Read an Erlang PID from the stream. + * + * @return the value of the PID. + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not an Erlang PID. + */ + public OtpErlangPid read_pid() throws OtpErlangDecodeException { + String node; + int id; + int serial; + int creation; + int tag; + + tag = read1skip_version(); + + if (tag != OtpExternal.pidTag) { + throw new OtpErlangDecodeException( + "Wrong tag encountered, expected " + OtpExternal.pidTag + + ", got " + tag); + } + + node = read_atom(); + id = read4BE() & 0x7fff; // 15 bits + serial = read4BE() & 0x1fff; // 13 bits + creation = read1() & 0x03; // 2 bits + + return new OtpErlangPid(node, id, serial, creation); + } + + /** + * Read an Erlang port from the stream. + * + * @return the value of the port. + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not an Erlang port. + */ + public OtpErlangPort read_port() throws OtpErlangDecodeException { + String node; + int id; + int creation; + int tag; + + tag = read1skip_version(); + + if (tag != OtpExternal.portTag) { + throw new OtpErlangDecodeException( + "Wrong tag encountered, expected " + OtpExternal.portTag + + ", got " + tag); + } + + node = read_atom(); + id = read4BE() & 0xfffffff; // 28 bits + creation = read1() & 0x03; // 2 bits + + return new OtpErlangPort(node, id, creation); + } + + /** + * Read an Erlang reference from the stream. + * + * @return the value of the reference + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not an Erlang reference. + */ + public OtpErlangRef read_ref() throws OtpErlangDecodeException { + String node; + int id; + int creation; + int tag; + + tag = read1skip_version(); + + switch (tag) { + case OtpExternal.refTag: + node = read_atom(); + id = read4BE() & 0x3ffff; // 18 bits + creation = read1() & 0x03; // 2 bits + return new OtpErlangRef(node, id, creation); + + case OtpExternal.newRefTag: + final int arity = read2BE(); + node = read_atom(); + creation = read1() & 0x03; // 2 bits + + final int[] ids = new int[arity]; + for (int i = 0; i < arity; i++) { + ids[i] = read4BE(); + } + ids[0] &= 0x3ffff; // first id gets truncated to 18 bits + return new OtpErlangRef(node, ids, creation); + + default: + throw new OtpErlangDecodeException( + "Wrong tag encountered, expected ref, got " + tag); + } + } + + public OtpErlangFun read_fun() throws OtpErlangDecodeException { + final int tag = read1skip_version(); + if (tag == OtpExternal.funTag) { + final int nFreeVars = read4BE(); + final OtpErlangPid pid = read_pid(); + final String module = read_atom(); + final long index = read_long(); + final long uniq = read_long(); + final OtpErlangObject[] freeVars = new OtpErlangObject[nFreeVars]; + for (int i = 0; i < nFreeVars; ++i) { + freeVars[i] = read_any(); + } + return new OtpErlangFun(pid, module, index, uniq, freeVars); + } else if (tag == OtpExternal.newFunTag) { + final int n = read4BE(); + final int arity = read1(); + final byte[] md5 = new byte[16]; + readN(md5); + final int index = read4BE(); + final int nFreeVars = read4BE(); + final String module = read_atom(); + final long oldIndex = read_long(); + final long uniq = read_long(); + final OtpErlangPid pid = read_pid(); + final OtpErlangObject[] freeVars = new OtpErlangObject[nFreeVars]; + for (int i = 0; i < nFreeVars; ++i) { + freeVars[i] = read_any(); + } + return new OtpErlangFun(pid, module, arity, md5, index, oldIndex, + uniq, freeVars); + } else { + throw new OtpErlangDecodeException( + "Wrong tag encountered, expected fun, got " + tag); + } + } + + public OtpErlangExternalFun read_external_fun() + throws OtpErlangDecodeException { + final int tag = read1skip_version(); + if (tag != OtpExternal.externalFunTag) { + throw new OtpErlangDecodeException( + "Wrong tag encountered, expected external fun, got " + tag); + } + final String module = read_atom(); + final String function = read_atom(); + final int arity = (int) read_long(); + return new OtpErlangExternalFun(module, function, arity); + } + + /** + * Read a string from the stream. + * + * @return the value of the string. + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not a string. + */ + public String read_string() throws OtpErlangDecodeException { + int tag; + int len; + byte[] strbuf; + int[] intbuf; + tag = read1skip_version(); + switch (tag) { + case OtpExternal.stringTag: + len = read2BE(); + strbuf = new byte[len]; + this.readN(strbuf); + return OtpErlangString.newString(strbuf); + case OtpExternal.nilTag: + return ""; + case OtpExternal.listTag: // List when unicode + + len = read4BE(); + intbuf = new int[len]; + for (int i = 0; i < len; i++) { + intbuf[i] = read_int(); + if (! OtpErlangString.isValidCodePoint(intbuf[i])) { + throw new OtpErlangDecodeException + ("Invalid CodePoint: " + intbuf[i]); + } + } + read_nil(); + return new String(intbuf, 0, intbuf.length); + default: + throw new OtpErlangDecodeException( + "Wrong tag encountered, expected " + OtpExternal.stringTag + + " or " + OtpExternal.listTag + ", got " + tag); + } + } + + /** + * Read a compressed term from the stream + * + * @return the resulting uncompressed term. + * + * @exception OtpErlangDecodeException + * if the next term in the stream is not a compressed term. + */ + public OtpErlangObject read_compressed() throws OtpErlangDecodeException { + final int tag = read1skip_version(); + + if (tag != OtpExternal.compressedTag) { + throw new OtpErlangDecodeException( + "Wrong tag encountered, expected " + + OtpExternal.compressedTag + ", got " + tag); + } + + final int size = read4BE(); + final byte[] buf = new byte[size]; + final java.util.zip.InflaterInputStream is = + new java.util.zip.InflaterInputStream(this); + try { + final int dsize = is.read(buf, 0, size); + if (dsize != size) { + throw new OtpErlangDecodeException("Decompression gave " + + dsize + " bytes, not " + size); + } + } catch (final IOException e) { + throw new OtpErlangDecodeException("Cannot read from input stream"); + } + + final OtpInputStream ois = new OtpInputStream(buf, flags); + return ois.read_any(); + } + + /** + * Read an arbitrary Erlang term from the stream. + * + * @return the Erlang term. + * + * @exception OtpErlangDecodeException + * if the stream does not contain a known Erlang type at the + * next position. + */ + public OtpErlangObject read_any() throws OtpErlangDecodeException { + // calls one of the above functions, depending on o + final int tag = peek1skip_version(); + + switch (tag) { + case OtpExternal.smallIntTag: + case OtpExternal.intTag: + case OtpExternal.smallBigTag: + case OtpExternal.largeBigTag: + return new OtpErlangLong(this); + + case OtpExternal.atomTag: + return new OtpErlangAtom(this); + + case OtpExternal.floatTag: + case OtpExternal.newFloatTag: + return new OtpErlangDouble(this); + + case OtpExternal.refTag: + case OtpExternal.newRefTag: + return new OtpErlangRef(this); + + case OtpExternal.portTag: + return new OtpErlangPort(this); + + case OtpExternal.pidTag: + return new OtpErlangPid(this); + + case OtpExternal.stringTag: + return new OtpErlangString(this); + + case OtpExternal.listTag: + case OtpExternal.nilTag: + if ((flags & DECODE_INT_LISTS_AS_STRINGS) != 0) { + final int savePos = getPos(); + try { + return new OtpErlangString(this); + } catch (final OtpErlangDecodeException e) { + } + setPos(savePos); + } + return new OtpErlangList(this); + + case OtpExternal.smallTupleTag: + case OtpExternal.largeTupleTag: + return new OtpErlangTuple(this); + + case OtpExternal.binTag: + return new OtpErlangBinary(this); + + case OtpExternal.bitBinTag: + return new OtpErlangBitstr(this); + + case OtpExternal.compressedTag: + return read_compressed(); + + case OtpExternal.newFunTag: + case OtpExternal.funTag: + return new OtpErlangFun(this); + + default: + throw new OtpErlangDecodeException("Uknown data type: " + tag); + } + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpLocalNode.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpLocalNode.java new file mode 100644 index 0000000000..fbd0eb4073 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpLocalNode.java @@ -0,0 +1,161 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +/** + * This class represents local node types. It is used to group the node types + * {@link OtpNode OtpNode} and {@link OtpSelf OtpSelf}. + */ +public class OtpLocalNode extends AbstractNode { + private int serial = 0; + private int pidCount = 1; + private int portCount = 1; + private int refId[]; + + protected int port; + protected java.net.Socket epmd; + + protected OtpLocalNode() { + super(); + init(); + } + + /** + * Create a node with the given name and the default cookie. + */ + protected OtpLocalNode(final String node) { + super(node); + init(); + } + + /** + * Create a node with the given name and cookie. + */ + protected OtpLocalNode(final String node, final String cookie) { + super(node, cookie); + init(); + } + + private void init() { + serial = 0; + pidCount = 1; + portCount = 1; + refId = new int[3]; + refId[0] = 1; + refId[1] = 0; + refId[2] = 0; + } + + /** + * Get the port number used by this node. + * + * @return the port number this server node is accepting connections on. + */ + public int port() { + return port; + } + + /** + * Set the Epmd socket after publishing this nodes listen port to Epmd. + * + * @param s + * The socket connecting this node to Epmd. + */ + protected void setEpmd(final java.net.Socket s) { + epmd = s; + } + + /** + * Get the Epmd socket. + * + * @return The socket connecting this node to Epmd. + */ + protected java.net.Socket getEpmd() { + return epmd; + } + + /** + * Create an Erlang {@link OtpErlangPid pid}. Erlang pids are based upon + * some node specific information; this method creates a pid using the + * information in this node. Each call to this method produces a unique pid. + * + * @return an Erlang pid. + */ + public synchronized OtpErlangPid createPid() { + final OtpErlangPid p = new OtpErlangPid(node, pidCount, serial, + creation); + + pidCount++; + if (pidCount > 0x7fff) { + pidCount = 0; + + serial++; + if (serial > 0x1fff) { /* 13 bits */ + serial = 0; + } + } + + return p; + } + + /** + * Create an Erlang {@link OtpErlangPort port}. Erlang ports are based upon + * some node specific information; this method creates a port using the + * information in this node. Each call to this method produces a unique + * port. It may not be meaningful to create a port in a non-Erlang + * environment, but this method is provided for completeness. + * + * @return an Erlang port. + */ + public synchronized OtpErlangPort createPort() { + final OtpErlangPort p = new OtpErlangPort(node, portCount, creation); + + portCount++; + if (portCount > 0xfffffff) { /* 28 bits */ + portCount = 0; + } + + return p; + } + + /** + * Create an Erlang {@link OtpErlangRef reference}. Erlang references are + * based upon some node specific information; this method creates a + * reference using the information in this node. Each call to this method + * produces a unique reference. + * + * @return an Erlang reference. + */ + public synchronized OtpErlangRef createRef() { + final OtpErlangRef r = new OtpErlangRef(node, refId, creation); + + // increment ref ids (3 ints: 18 + 32 + 32 bits) + refId[0]++; + if (refId[0] > 0x3ffff) { + refId[0] = 0; + + refId[1]++; + if (refId[1] == 0) { + refId[2]++; + } + } + + return r; + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMD5.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMD5.java new file mode 100644 index 0000000000..903a446258 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMD5.java @@ -0,0 +1,354 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +// package scope +class OtpMD5 { + + /* + * * MD5 constants + */ + static final long S11 = 7; + static final long S12 = 12; + static final long S13 = 17; + static final long S14 = 22; + static final long S21 = 5; + static final long S22 = 9; + static final long S23 = 14; + static final long S24 = 20; + static final long S31 = 4; + static final long S32 = 11; + static final long S33 = 16; + static final long S34 = 23; + static final long S41 = 6; + static final long S42 = 10; + static final long S43 = 15; + static final long S44 = 21; + + /* + * Has to be this large to avoid sign problems + */ + + private final long state[] = { 0x67452301L, 0xefcdab89L, 0x98badcfeL, + 0x10325476L }; + private final long count[] = { 0L, 0L }; + private final int buffer[]; + + public OtpMD5() { + buffer = new int[64]; + int i; + for (i = 0; i < 64; ++i) { + buffer[i] = 0; + } + } + + private int[] to_bytes(final String s) { + final char tmp[] = s.toCharArray(); + final int ret[] = new int[tmp.length]; + int i; + + for (i = 0; i < tmp.length; ++i) { + ret[i] = tmp[i] & 0xFF; + } + return ret; + } + + private int[] clean_bytes(final int bytes[]) { + final int ret[] = new int[bytes.length]; + int i; + + for (i = 0; i < bytes.length; ++i) { + ret[i] = bytes[i] & 0xFF; + } + return ret; + } + + /* + * * A couple of operations where 32 bit over/under-flow is expected + */ + + private long shl(final long what, final int steps) { + return what << steps & 0xFFFFFFFFL; + } + + private long shr(final long what, final int steps) { + return what >>> steps; + } + + private long plus(final long a, final long b) { + return a + b & 0xFFFFFFFFL; + } + + private long not(long x) { + return ~x & 0xFFFFFFFFL; + } + + private void to_buffer(int to_start, final int[] from, int from_start, + int num) { + while (num-- > 0) { + buffer[to_start++] = from[from_start++]; + } + } + + private void do_update(final int bytes[]) { + int index = (int) (count[0] >>> 3 & 0x3F); + final long inlen = bytes.length; + final long addcount = shl(inlen, 3); + final long partlen = 64 - index; + int i; + + count[0] = plus(count[0], addcount); + + if (count[0] < addcount) { + ++count[1]; + } + + count[1] = plus(count[1], shr(inlen, 29)); + + /* dumpstate(); */ + + if (inlen >= partlen) { + to_buffer(index, bytes, 0, (int) partlen); + transform(buffer, 0); + + for (i = (int) partlen; i + 63 < inlen; i += 64) { + transform(bytes, i); + } + + index = 0; + } else { + i = 0; + } + + /* dumpstate(); */ + + to_buffer(index, bytes, i, (int) inlen - i); + + /* dumpstate(); */ + + } + + private void dumpstate() { + System.out.println("state = {" + state[0] + ", " + state[1] + ", " + + state[2] + ", " + state[3] + "}"); + System.out.println("count = {" + count[0] + ", " + count[1] + "}"); + System.out.print("buffer = {"); + int i; + for (i = 0; i < 64; ++i) { + if (i > 0) { + System.out.print(", "); + } + System.out.print(buffer[i]); + } + System.out.println("}"); + } + + /* + * * The transformation functions + */ + + private long F(final long x, final long y, final long z) { + return x & y | not(x) & z; + } + + private long G(final long x, final long y, final long z) { + return x & z | y & not(z); + } + + private long H(final long x, final long y, final long z) { + return x ^ y ^ z; + } + + private long I(final long x, final long y, final long z) { + return y ^ (x | not(z)); + } + + private long ROTATE_LEFT(final long x, final long n) { + return shl(x, (int) n) | shr(x, (int) (32 - n)); + } + + private long FF(long a, final long b, final long c, final long d, + final long x, final long s, final long ac) { + a = plus(a, plus(plus(F(b, c, d), x), ac)); + a = ROTATE_LEFT(a, s); + return plus(a, b); + } + + private long GG(long a, final long b, final long c, final long d, + final long x, final long s, final long ac) { + a = plus(a, plus(plus(G(b, c, d), x), ac)); + a = ROTATE_LEFT(a, s); + return plus(a, b); + } + + private long HH(long a, final long b, final long c, final long d, + final long x, final long s, final long ac) { + a = plus(a, plus(plus(H(b, c, d), x), ac)); + a = ROTATE_LEFT(a, s); + return plus(a, b); + } + + private long II(long a, final long b, final long c, final long d, + final long x, final long s, final long ac) { + a = plus(a, plus(plus(I(b, c, d), x), ac)); + a = ROTATE_LEFT(a, s); + return plus(a, b); + } + + private void decode(final long output[], final int input[], + final int in_from, final int len) { + int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[i] = input[j + in_from] | shl(input[j + in_from + 1], 8) + | shl(input[j + in_from + 2], 16) + | shl(input[j + in_from + 3], 24); + } + } + + private void transform(final int block[], final int from) { + long a = state[0]; + long b = state[1]; + long c = state[2]; + long d = state[3]; + final long x[] = new long[16]; + + decode(x, block, from, 64); + + a = FF(a, b, c, d, x[0], S11, 0xd76aa478L); /* 1 */ + d = FF(d, a, b, c, x[1], S12, 0xe8c7b756L); /* 2 */ + c = FF(c, d, a, b, x[2], S13, 0x242070dbL); /* 3 */ + b = FF(b, c, d, a, x[3], S14, 0xc1bdceeeL); /* 4 */ + a = FF(a, b, c, d, x[4], S11, 0xf57c0fafL); /* 5 */ + d = FF(d, a, b, c, x[5], S12, 0x4787c62aL); /* 6 */ + c = FF(c, d, a, b, x[6], S13, 0xa8304613L); /* 7 */ + b = FF(b, c, d, a, x[7], S14, 0xfd469501L); /* 8 */ + a = FF(a, b, c, d, x[8], S11, 0x698098d8L); /* 9 */ + d = FF(d, a, b, c, x[9], S12, 0x8b44f7afL); /* 10 */ + c = FF(c, d, a, b, x[10], S13, 0xffff5bb1L); /* 11 */ + b = FF(b, c, d, a, x[11], S14, 0x895cd7beL); /* 12 */ + a = FF(a, b, c, d, x[12], S11, 0x6b901122L); /* 13 */ + d = FF(d, a, b, c, x[13], S12, 0xfd987193L); /* 14 */ + c = FF(c, d, a, b, x[14], S13, 0xa679438eL); /* 15 */ + b = FF(b, c, d, a, x[15], S14, 0x49b40821L); /* 16 */ + + /* Round 2 */ + a = GG(a, b, c, d, x[1], S21, 0xf61e2562L); /* 17 */ + d = GG(d, a, b, c, x[6], S22, 0xc040b340L); /* 18 */ + c = GG(c, d, a, b, x[11], S23, 0x265e5a51L); /* 19 */ + b = GG(b, c, d, a, x[0], S24, 0xe9b6c7aaL); /* 20 */ + a = GG(a, b, c, d, x[5], S21, 0xd62f105dL); /* 21 */ + d = GG(d, a, b, c, x[10], S22, 0x2441453L); /* 22 */ + c = GG(c, d, a, b, x[15], S23, 0xd8a1e681L); /* 23 */ + b = GG(b, c, d, a, x[4], S24, 0xe7d3fbc8L); /* 24 */ + a = GG(a, b, c, d, x[9], S21, 0x21e1cde6L); /* 25 */ + d = GG(d, a, b, c, x[14], S22, 0xc33707d6L); /* 26 */ + c = GG(c, d, a, b, x[3], S23, 0xf4d50d87L); /* 27 */ + b = GG(b, c, d, a, x[8], S24, 0x455a14edL); /* 28 */ + a = GG(a, b, c, d, x[13], S21, 0xa9e3e905L); /* 29 */ + d = GG(d, a, b, c, x[2], S22, 0xfcefa3f8L); /* 30 */ + c = GG(c, d, a, b, x[7], S23, 0x676f02d9L); /* 31 */ + b = GG(b, c, d, a, x[12], S24, 0x8d2a4c8aL); /* 32 */ + + /* Round 3 */ + a = HH(a, b, c, d, x[5], S31, 0xfffa3942L); /* 33 */ + d = HH(d, a, b, c, x[8], S32, 0x8771f681L); /* 34 */ + c = HH(c, d, a, b, x[11], S33, 0x6d9d6122L); /* 35 */ + b = HH(b, c, d, a, x[14], S34, 0xfde5380cL); /* 36 */ + a = HH(a, b, c, d, x[1], S31, 0xa4beea44L); /* 37 */ + d = HH(d, a, b, c, x[4], S32, 0x4bdecfa9L); /* 38 */ + c = HH(c, d, a, b, x[7], S33, 0xf6bb4b60L); /* 39 */ + b = HH(b, c, d, a, x[10], S34, 0xbebfbc70L); /* 40 */ + a = HH(a, b, c, d, x[13], S31, 0x289b7ec6L); /* 41 */ + d = HH(d, a, b, c, x[0], S32, 0xeaa127faL); /* 42 */ + c = HH(c, d, a, b, x[3], S33, 0xd4ef3085L); /* 43 */ + b = HH(b, c, d, a, x[6], S34, 0x4881d05L); /* 44 */ + a = HH(a, b, c, d, x[9], S31, 0xd9d4d039L); /* 45 */ + d = HH(d, a, b, c, x[12], S32, 0xe6db99e5L); /* 46 */ + c = HH(c, d, a, b, x[15], S33, 0x1fa27cf8L); /* 47 */ + b = HH(b, c, d, a, x[2], S34, 0xc4ac5665L); /* 48 */ + + /* Round 4 */ + a = II(a, b, c, d, x[0], S41, 0xf4292244L); /* 49 */ + d = II(d, a, b, c, x[7], S42, 0x432aff97L); /* 50 */ + c = II(c, d, a, b, x[14], S43, 0xab9423a7L); /* 51 */ + b = II(b, c, d, a, x[5], S44, 0xfc93a039L); /* 52 */ + a = II(a, b, c, d, x[12], S41, 0x655b59c3L); /* 53 */ + d = II(d, a, b, c, x[3], S42, 0x8f0ccc92L); /* 54 */ + c = II(c, d, a, b, x[10], S43, 0xffeff47dL); /* 55 */ + b = II(b, c, d, a, x[1], S44, 0x85845dd1L); /* 56 */ + a = II(a, b, c, d, x[8], S41, 0x6fa87e4fL); /* 57 */ + d = II(d, a, b, c, x[15], S42, 0xfe2ce6e0L); /* 58 */ + c = II(c, d, a, b, x[6], S43, 0xa3014314L); /* 59 */ + b = II(b, c, d, a, x[13], S44, 0x4e0811a1L); /* 60 */ + a = II(a, b, c, d, x[4], S41, 0xf7537e82L); /* 61 */ + d = II(d, a, b, c, x[11], S42, 0xbd3af235L); /* 62 */ + c = II(c, d, a, b, x[2], S43, 0x2ad7d2bbL); /* 63 */ + b = II(b, c, d, a, x[9], S44, 0xeb86d391L); /* 64 */ + + state[0] = plus(state[0], a); + state[1] = plus(state[1], b); + state[2] = plus(state[2], c); + state[3] = plus(state[3], d); + } + + public void update(final int bytes[]) { + do_update(clean_bytes(bytes)); + } + + public void update(final String s) { + do_update(to_bytes(s)); + } + + private int[] encode(final long[] input, final int len) { + final int output[] = new int[len]; + int i, j; + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (int) (input[i] & 0xff); + output[j + 1] = (int) (input[i] >>> 8 & 0xff); + output[j + 2] = (int) (input[i] >>> 16 & 0xff); + output[j + 3] = (int) (input[i] >>> 24 & 0xff); + } + return output; + } + + public int[] final_bytes() { + final int bits[] = encode(count, 8); + int index, padlen; + int padding[], i; + int[] digest; + + index = (int) (count[0] >>> 3 & 0x3f); + padlen = index < 56 ? 56 - index : 120 - index; + /* padlen > 0 */ + padding = new int[padlen]; + padding[0] = 0x80; + for (i = 1; i < padlen; ++i) { + padding[i] = 0; + } + + do_update(padding); + + do_update(bits); + + digest = encode(state, 16); + + return digest; + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMbox.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMbox.java new file mode 100644 index 0000000000..4146bd3ced --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMbox.java @@ -0,0 +1,722 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +/** + * <p> + * Provides a simple mechanism for exchanging messages with Erlang processes or + * other instances of this class. + * </p> + * + * <p> + * Each mailbox is associated with a unique {@link OtpErlangPid pid} that + * contains information necessary for delivery of messages. When sending + * messages to named processes or mailboxes, the sender pid is made available to + * the recipient of the message. When sending messages to other mailboxes, the + * recipient can only respond if the sender includes the pid as part of the + * message contents. The sender can determine his own pid by calling + * {@link #self self()}. + * </p> + * + * <p> + * Mailboxes can be named, either at creation or later. Messages can be sent to + * named mailboxes and named Erlang processes without knowing the + * {@link OtpErlangPid pid} that identifies the mailbox. This is neccessary in + * order to set up initial communication between parts of an application. Each + * mailbox can have at most one name. + * </p> + * + * <p> + * Since this class was intended for communication with Erlang, all of the send + * methods take {@link OtpErlangObject OtpErlangObject} arguments. However this + * class can also be used to transmit arbitrary Java objects (as long as they + * implement one of java.io.Serializable or java.io.Externalizable) by + * encapsulating the object in a {@link OtpErlangBinary OtpErlangBinary}. + * </p> + * + * <p> + * Messages to remote nodes are externalized for transmission, and as a result + * the recipient receives a <b>copy</b> of the original Java object. To ensure + * consistent behaviour when messages are sent between local mailboxes, such + * messages are cloned before delivery. + * </p> + * + * <p> + * Additionally, mailboxes can be linked in much the same way as Erlang + * processes. If a link is active when a mailbox is {@link #close closed}, any + * linked Erlang processes or OtpMboxes will be sent an exit signal. As well, + * exit signals will be (eventually) sent if a mailbox goes out of scope and its + * {@link #finalize finalize()} method called. However due to the nature of + * finalization (i.e. Java makes no guarantees about when {@link #finalize + * finalize()} will be called) it is recommended that you always explicitly + * close mailboxes if you are using links instead of relying on finalization to + * notify other parties in a timely manner. + * </p> + * + * When retrieving messages from a mailbox that has received an exit signal, an + * {@link OtpErlangExit OtpErlangExit} exception will be raised. Note that the + * exception is queued in the mailbox along with other messages, and will not be + * raised until it reaches the head of the queue and is about to be retrieved. + * </p> + * + */ +public class OtpMbox { + OtpNode home; + OtpErlangPid self; + GenericQueue queue; + String name; + Links links; + + // package constructor: called by OtpNode:createMbox(name) + // to create a named mbox + OtpMbox(final OtpNode home, final OtpErlangPid self, final String name) { + this.self = self; + this.home = home; + this.name = name; + queue = new GenericQueue(); + links = new Links(10); + } + + // package constructor: called by OtpNode:createMbox() + // to create an anonymous + OtpMbox(final OtpNode home, final OtpErlangPid self) { + this(home, self, null); + } + + /** + * <p> + * Get the identifying {@link OtpErlangPid pid} associated with this + * mailbox. + * </p> + * + * <p> + * The {@link OtpErlangPid pid} associated with this mailbox uniquely + * identifies the mailbox and can be used to address the mailbox. You can + * send the {@link OtpErlangPid pid} to a remote communicating part so that + * he can know where to send his response. + * </p> + * + * @return the self pid for this mailbox. + */ + public OtpErlangPid self() { + return self; + } + + /** + * <p> + * Register or remove a name for this mailbox. Registering a name for a + * mailbox enables others to send messages without knowing the + * {@link OtpErlangPid pid} of the mailbox. A mailbox can have at most one + * name; if the mailbox already had a name, calling this method will + * supercede that name. + * </p> + * + * @param name + * the name to register for the mailbox. Specify null to + * unregister the existing name from this mailbox. + * + * @return true if the name was available, or false otherwise. + */ + public synchronized boolean registerName(final String name) { + return home.registerName(name, this); + } + + /** + * Get the registered name of this mailbox. + * + * @return the registered name of this mailbox, or null if the mailbox had + * no registerd name. + */ + public String getName() { + return name; + } + + /** + * Block until a message arrives for this mailbox. + * + * @return an {@link OtpErlangObject OtpErlangObject} representing the body + * of the next message waiting in this mailbox. + * + * @exception OtpErlangDecodeException + * if the message can not be decoded. + * + * @exception OtpErlangExit + * if a linked {@link OtpErlangPid pid} has exited or has + * sent an exit signal to this mailbox. + */ + public OtpErlangObject receive() throws OtpErlangExit, + OtpErlangDecodeException { + try { + return receiveMsg().getMsg(); + } catch (final OtpErlangExit e) { + throw e; + } catch (final OtpErlangDecodeException f) { + throw f; + } + } + + /** + * Wait for a message to arrive for this mailbox. + * + * @param timeout + * the time, in milliseconds, to wait for a message before + * returning null. + * + * @return an {@link OtpErlangObject OtpErlangObject} representing the body + * of the next message waiting in this mailbox. + * + * @exception OtpErlangDecodeException + * if the message can not be decoded. + * + * @exception OtpErlangExit + * if a linked {@link OtpErlangPid pid} has exited or has + * sent an exit signal to this mailbox. + */ + public OtpErlangObject receive(final long timeout) throws OtpErlangExit, + OtpErlangDecodeException { + try { + final OtpMsg m = receiveMsg(timeout); + if (m != null) { + return m.getMsg(); + } + } catch (final OtpErlangExit e) { + throw e; + } catch (final OtpErlangDecodeException f) { + throw f; + } catch (final InterruptedException g) { + } + return null; + } + + /** + * Block until a message arrives for this mailbox. + * + * @return a byte array representing the still-encoded body of the next + * message waiting in this mailbox. + * + * @exception OtpErlangExit + * if a linked {@link OtpErlangPid pid} has exited or has + * sent an exit signal to this mailbox. + * + */ + public OtpInputStream receiveBuf() throws OtpErlangExit { + return receiveMsg().getMsgBuf(); + } + + /** + * Wait for a message to arrive for this mailbox. + * + * @param timeout + * the time, in milliseconds, to wait for a message before + * returning null. + * + * @return a byte array representing the still-encoded body of the next + * message waiting in this mailbox. + * + * @exception OtpErlangExit + * if a linked {@link OtpErlangPid pid} has exited or has + * sent an exit signal to this mailbox. + * + * @exception InterruptedException + * if no message if the method times out before a message + * becomes available. + */ + public OtpInputStream receiveBuf(final long timeout) + throws InterruptedException, OtpErlangExit { + final OtpMsg m = receiveMsg(timeout); + if (m != null) { + return m.getMsgBuf(); + } + + return null; + } + + /** + * Block until a message arrives for this mailbox. + * + * @return an {@link OtpMsg OtpMsg} containing the header information as + * well as the body of the next message waiting in this mailbox. + * + * @exception OtpErlangExit + * if a linked {@link OtpErlangPid pid} has exited or has + * sent an exit signal to this mailbox. + * + */ + public OtpMsg receiveMsg() throws OtpErlangExit { + + final OtpMsg m = (OtpMsg) queue.get(); + + switch (m.type()) { + case OtpMsg.exitTag: + case OtpMsg.exit2Tag: + try { + final OtpErlangObject o = m.getMsg(); + throw new OtpErlangExit(o, m.getSenderPid()); + } catch (final OtpErlangDecodeException e) { + throw new OtpErlangExit("unknown", m.getSenderPid()); + } + + default: + return m; + } + } + + /** + * Wait for a message to arrive for this mailbox. + * + * @param timeout + * the time, in milliseconds, to wait for a message. + * + * @return an {@link OtpMsg OtpMsg} containing the header information as + * well as the body of the next message waiting in this mailbox. + * + * @exception OtpErlangExit + * if a linked {@link OtpErlangPid pid} has exited or has + * sent an exit signal to this mailbox. + * + * @exception InterruptedException + * if no message if the method times out before a message + * becomes available. + */ + public OtpMsg receiveMsg(final long timeout) throws InterruptedException, + OtpErlangExit { + final OtpMsg m = (OtpMsg) queue.get(timeout); + + if (m == null) { + return null; + } + + switch (m.type()) { + case OtpMsg.exitTag: + case OtpMsg.exit2Tag: + try { + final OtpErlangObject o = m.getMsg(); + throw new OtpErlangExit(o, m.getSenderPid()); + } catch (final OtpErlangDecodeException e) { + throw new OtpErlangExit("unknown", m.getSenderPid()); + } + + default: + return m; + } + } + + /** + * Send a message to a remote {@link OtpErlangPid pid}, representing either + * another {@link OtpMbox mailbox} or an Erlang process. + * + * @param to + * the {@link OtpErlangPid pid} identifying the intended + * recipient of the message. + * + * @param msg + * the body of the message to send. + * + */ + public void send(final OtpErlangPid to, final OtpErlangObject msg) { + try { + final String node = to.node(); + if (node.equals(home.node())) { + home.deliver(new OtpMsg(to, (OtpErlangObject) msg.clone())); + } else { + final OtpCookedConnection conn = home.getConnection(node); + if (conn == null) { + return; + } + conn.send(self, to, msg); + } + } catch (final Exception e) { + } + } + + /** + * Send a message to a named mailbox created from the same node as this + * mailbox. + * + * @param name + * the registered name of recipient mailbox. + * + * @param msg + * the body of the message to send. + * + */ + public void send(final String name, final OtpErlangObject msg) { + home.deliver(new OtpMsg(self, name, (OtpErlangObject) msg.clone())); + } + + /** + * Send a message to a named mailbox created from another node. + * + * @param name + * the registered name of recipient mailbox. + * + * @param node + * the name of the remote node where the recipient mailbox is + * registered. + * + * @param msg + * the body of the message to send. + * + */ + public void send(final String name, final String node, + final OtpErlangObject msg) { + try { + final String currentNode = home.node(); + if (node.equals(currentNode)) { + send(name, msg); + } else if (node.indexOf('@', 0) < 0 + && node.equals(currentNode.substring(0, currentNode + .indexOf('@', 0)))) { + send(name, msg); + } else { + // other node + final OtpCookedConnection conn = home.getConnection(node); + if (conn == null) { + return; + } + conn.send(self, name, msg); + } + } catch (final Exception e) { + } + } + + /** + * Close this mailbox with the given reason. + * + * <p> + * After this operation, the mailbox will no longer be able to receive + * messages. Any delivered but as yet unretrieved messages can still be + * retrieved however. + * </p> + * + * <p> + * If there are links from this mailbox to other {@link OtpErlangPid pids}, + * they will be broken when this method is called and exit signals will be + * sent. + * </p> + * + * @param reason + * an Erlang term describing the reason for the exit. + */ + public void exit(final OtpErlangObject reason) { + home.closeMbox(this, reason); + } + + /** + * Equivalent to <code>exit(new OtpErlangAtom(reason))</code>. + * </p> + * + * @see #exit(OtpErlangObject) + */ + public void exit(final String reason) { + exit(new OtpErlangAtom(reason)); + } + + /** + * <p> + * Send an exit signal to a remote {@link OtpErlangPid pid}. This method + * does not cause any links to be broken, except indirectly if the remote + * {@link OtpErlangPid pid} exits as a result of this exit signal. + * </p> + * + * @param to + * the {@link OtpErlangPid pid} to which the exit signal + * should be sent. + * + * @param reason + * an Erlang term indicating the reason for the exit. + */ + // it's called exit, but it sends exit2 + public void exit(final OtpErlangPid to, final OtpErlangObject reason) { + exit(2, to, reason); + } + + /** + * <p> + * Equivalent to <code>exit(to, new + * OtpErlangAtom(reason))</code>. + * </p> + * + * @see #exit(OtpErlangPid, OtpErlangObject) + */ + public void exit(final OtpErlangPid to, final String reason) { + exit(to, new OtpErlangAtom(reason)); + } + + // this function used internally when "process" dies + // since Erlang discerns between exit and exit/2. + private void exit(final int arity, final OtpErlangPid to, + final OtpErlangObject reason) { + try { + final String node = to.node(); + if (node.equals(home.node())) { + home.deliver(new OtpMsg(OtpMsg.exitTag, self, to, reason)); + } else { + final OtpCookedConnection conn = home.getConnection(node); + if (conn == null) { + return; + } + switch (arity) { + case 1: + conn.exit(self, to, reason); + break; + + case 2: + conn.exit2(self, to, reason); + break; + } + } + } catch (final Exception e) { + } + } + + /** + * <p> + * Link to a remote mailbox or Erlang process. Links are idempotent, calling + * this method multiple times will not result in more than one link being + * created. + * </p> + * + * <p> + * If the remote process subsequently exits or the mailbox is closed, a + * subsequent attempt to retrieve a message through this mailbox will cause + * an {@link OtpErlangExit OtpErlangExit} exception to be raised. Similarly, + * if the sending mailbox is closed, the linked mailbox or process will + * receive an exit signal. + * </p> + * + * <p> + * If the remote process cannot be reached in order to set the link, the + * exception is raised immediately. + * </p> + * + * @param to + * the {@link OtpErlangPid pid} representing the object to + * link to. + * + * @exception OtpErlangExit + * if the {@link OtpErlangPid pid} referred to does not + * exist or could not be reached. + * + */ + public void link(final OtpErlangPid to) throws OtpErlangExit { + try { + final String node = to.node(); + if (node.equals(home.node())) { + if (!home.deliver(new OtpMsg(OtpMsg.linkTag, self, to))) { + throw new OtpErlangExit("noproc", to); + } + } else { + final OtpCookedConnection conn = home.getConnection(node); + if (conn != null) { + conn.link(self, to); + } else { + throw new OtpErlangExit("noproc", to); + } + } + } catch (final OtpErlangExit e) { + throw e; + } catch (final Exception e) { + } + + links.addLink(self, to); + } + + /** + * <p> + * Remove a link to a remote mailbox or Erlang process. This method removes + * a link created with {@link #link link()}. Links are idempotent; calling + * this method once will remove all links between this mailbox and the + * remote {@link OtpErlangPid pid}. + * </p> + * + * @param to + * the {@link OtpErlangPid pid} representing the object to + * unlink from. + * + */ + public void unlink(final OtpErlangPid to) { + links.removeLink(self, to); + + try { + final String node = to.node(); + if (node.equals(home.node())) { + home.deliver(new OtpMsg(OtpMsg.unlinkTag, self, to)); + } else { + final OtpCookedConnection conn = home.getConnection(node); + if (conn != null) { + conn.unlink(self, to); + } + } + } catch (final Exception e) { + } + } + + /** + * <p> + * Create a connection to a remote node. + * </p> + * + * <p> + * Strictly speaking, this method is not necessary simply to set up a + * connection, since connections are created automatically first time a + * message is sent to a {@link OtpErlangPid pid} on the remote node. + * </p> + * + * <p> + * This method makes it possible to wait for a node to come up, however, or + * check that a node is still alive. + * </p> + * + * <p> + * This method calls a method with the same name in {@link OtpNode#ping + * Otpnode} but is provided here for convenience. + * </p> + * + * @param node + * the name of the node to ping. + * + * @param timeout + * the time, in milliseconds, before reporting failure. + */ + public boolean ping(final String node, final long timeout) { + return home.ping(node, timeout); + } + + /** + * <p> + * Get a list of all known registered names on the same {@link OtpNode node} + * as this mailbox. + * </p> + * + * <p> + * This method calls a method with the same name in {@link OtpNode#getNames + * Otpnode} but is provided here for convenience. + * </p> + * + * @return an array of Strings containing all registered names on this + * {@link OtpNode node}. + */ + public String[] getNames() { + return home.getNames(); + } + + /** + * Determine the {@link OtpErlangPid pid} corresponding to a registered name + * on this {@link OtpNode node}. + * + * <p> + * This method calls a method with the same name in {@link OtpNode#whereis + * Otpnode} but is provided here for convenience. + * </p> + * + * @return the {@link OtpErlangPid pid} corresponding to the registered + * name, or null if the name is not known on this node. + */ + public OtpErlangPid whereis(final String name) { + return home.whereis(name); + } + + /** + * Close this mailbox. + * + * <p> + * After this operation, the mailbox will no longer be able to receive + * messages. Any delivered but as yet unretrieved messages can still be + * retrieved however. + * </p> + * + * <p> + * If there are links from this mailbox to other {@link OtpErlangPid pids}, + * they will be broken when this method is called and exit signals with + * reason 'normal' will be sent. + * </p> + * + * <p> + * This is equivalent to {@link #exit(String) exit("normal")}. + * </p> + */ + public void close() { + home.closeMbox(this); + } + + @Override + protected void finalize() { + close(); + queue.flush(); + } + + /** + * Determine if two mailboxes are equal. + * + * @return true if both Objects are mailboxes with the same identifying + * {@link OtpErlangPid pids}. + */ + @Override + public boolean equals(final Object o) { + if (!(o instanceof OtpMbox)) { + return false; + } + + final OtpMbox m = (OtpMbox) o; + return m.self.equals(self); + } + + /* + * called by OtpNode to deliver message to this mailbox. + * + * About exit and exit2: both cause exception to be raised upon receive(). + * However exit (not 2) causes any link to be removed as well, while exit2 + * leaves any links intact. + */ + void deliver(final OtpMsg m) { + switch (m.type()) { + case OtpMsg.linkTag: + links.addLink(self, m.getSenderPid()); + break; + + case OtpMsg.unlinkTag: + links.removeLink(self, m.getSenderPid()); + break; + + case OtpMsg.exitTag: + links.removeLink(self, m.getSenderPid()); + queue.put(m); + break; + + case OtpMsg.exit2Tag: + default: + queue.put(m); + break; + } + } + + // used to break all known links to this mbox + void breakLinks(final OtpErlangObject reason) { + final Link[] l = links.clearLinks(); + + if (l != null) { + final int len = l.length; + + for (int i = 0; i < len; i++) { + exit(1, l[i].remote(), reason); + } + } + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMsg.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMsg.java new file mode 100644 index 0000000000..80d8a5ccae --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpMsg.java @@ -0,0 +1,291 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +/** + * <p> + * Provides a carrier for Erlang messages. + * </p> + * + * <p> + * Instances of this class are created to package header and payload information + * in received Erlang messages so that the recipient can obtain both parts with + * a single call to {@link OtpMbox#receiveMsg receiveMsg()}. + * </p> + * + * <p> + * The header information that is available is as follows: <lu> + * <li> a tag indicating the type of message + * <li> the intended recipient of the message, either as a + * {@link OtpErlangPid pid} or as a String, but never both. + * <li> (sometimes) the sender of the message. Due to some eccentric + * characteristics of the Erlang distribution protocol, not all messages have + * information about the sending process. In particular, only messages whose tag + * is {@link OtpMsg#regSendTag regSendTag} contain sender information. </lu> + * + * <p> + * Message are sent using the Erlang external format (see separate + * documentation). When a message is received and delivered to the recipient + * {@link OtpMbox mailbox}, the body of the message is still in this external + * representation until {@link #getMsg getMsg()} is called, at which point the + * message is decoded. A copy of the decoded message is stored in the OtpMsg so + * that subsequent calls to {@link #getMsg getMsg()} do not require that the + * message be decoded a second time. + * </p> + */ +public class OtpMsg { + public static final int linkTag = 1; + public static final int sendTag = 2; + public static final int exitTag = 3; + public static final int unlinkTag = 4; + /* public static final int nodeLinkTag = 5; */ + public static final int regSendTag = 6; + /* public static final int groupLeaderTag = 7; */ + public static final int exit2Tag = 8; + + protected int tag; // what type of message is this (send, link, exit etc) + protected OtpInputStream paybuf; + protected OtpErlangObject payload; + + protected OtpErlangPid from; + protected OtpErlangPid to; + protected String toName; + + // send has receiver pid but no sender information + OtpMsg(final OtpErlangPid to, final OtpInputStream paybuf) { + tag = sendTag; + from = null; + this.to = to; + toName = null; + this.paybuf = paybuf; + payload = null; + } + + // send has receiver pid but no sender information + OtpMsg(final OtpErlangPid to, final OtpErlangObject payload) { + tag = sendTag; + from = null; + this.to = to; + toName = null; + paybuf = null; + this.payload = payload; + } + + // send_reg has sender pid and receiver name + OtpMsg(final OtpErlangPid from, final String toName, + final OtpInputStream paybuf) { + tag = regSendTag; + this.from = from; + this.toName = toName; + to = null; + this.paybuf = paybuf; + payload = null; + } + + // send_reg has sender pid and receiver name + OtpMsg(final OtpErlangPid from, final String toName, + final OtpErlangObject payload) { + tag = regSendTag; + this.from = from; + this.toName = toName; + to = null; + paybuf = null; + this.payload = payload; + } + + // exit (etc) has from, to, reason + OtpMsg(final int tag, final OtpErlangPid from, final OtpErlangPid to, + final OtpErlangObject reason) { + this.tag = tag; + this.from = from; + this.to = to; + paybuf = null; + payload = reason; + } + + // special case when reason is an atom (i.e. most of the time) + OtpMsg(final int tag, final OtpErlangPid from, final OtpErlangPid to, + final String reason) { + this.tag = tag; + this.from = from; + this.to = to; + paybuf = null; + payload = new OtpErlangAtom(reason); + } + + // other message types (link, unlink) + OtpMsg(int tag, final OtpErlangPid from, final OtpErlangPid to) { + // convert TT-tags to equiv non-TT versions + if (tag > 10) { + tag -= 10; + } + + this.tag = tag; + this.from = from; + this.to = to; + } + + /** + * Get the payload from this message without deserializing it. + * + * @return the serialized Erlang term contained in this message. + * + */ + OtpInputStream getMsgBuf() { + return paybuf; + } + + /** + * <p> + * Get the type marker from this message. The type marker identifies the + * type of message. Valid values are the ``tag'' constants defined in this + * class. + * </p> + * + * <p> + * The tab identifies not only the type of message but also the content of + * the OtpMsg object, since different messages have different components, as + * follows: + * </p> + * + * <ul> + * <li> sendTag identifies a "normal" message. The recipient is a + * {@link OtpErlangPid Pid} and it is available through {@link + * #getRecipientPid getRecipientPid()}. Sender information is not available. + * The message body can be retrieved with {@link #getMsg getMsg()}. </li> + * + * <li> regSendTag also identifies a "normal" message. The recipient here is + * a String and it is available through {@link #getRecipientName + * getRecipientName()}. Sender information is available through + * #getSenderPid getSenderPid()}. The message body can be retrieved with + * {@link #getMsg getMsg()}. </li> + * + * <li> linkTag identifies a link request. The Pid of the sender is + * available, as well as the Pid to which the link should be made. </li> + * + * <li> exitTag and exit2Tag messages are sent as a result of broken links. + * Both sender and recipient Pids and are available through the + * corresponding methods, and the "reason" is available through + * {@link #getMsg getMsg()}. </li> + * </ul> + */ + public int type() { + return tag; + } + + /** + * <p> + * Deserialize and return a new copy of the message contained in this + * OtpMsg. + * </p> + * + * <p> + * The first time this method is called the actual payload is deserialized + * and the Erlang term is created. Calling this method subsequent times will + * not cuase the message to be deserialized additional times, instead the + * same Erlang term object will be returned. + * </p> + * + * @return an Erlang term. + * + * @exception OtpErlangDecodeException + * if the byte stream could not be deserialized. + * + */ + public OtpErlangObject getMsg() throws OtpErlangDecodeException { + if (payload == null) { + payload = paybuf.read_any(); + } + return payload; + } + + /** + * <p> + * Get the name of the recipient for this message. + * </p> + * + * <p> + * Messages are sent to Pids or names. If this message was sent to a name + * then the name is returned by this method. + * </p> + * + * @return the name of the recipient, or null if the recipient was in fact a + * Pid. + */ + public String getRecipientName() { + return toName; + } + + /** + * <p> + * Get the Pid of the recipient for this message, if it is a sendTag + * message. + * </p> + * + * <p> + * Messages are sent to Pids or names. If this message was sent to a Pid + * then the Pid is returned by this method. The recipient Pid is also + * available for link, unlink and exit messages. + * </p> + * + * @return the Pid of the recipient, or null if the recipient was in fact a + * name. + */ + public OtpErlangPid getRecipientPid() { + return to; + } + + /** + * <p> + * Get the name of the recipient for this message, if it is a regSendTag + * message. + * </p> + * + * <p> + * Messages are sent to Pids or names. If this message was sent to a name + * then the name is returned by this method. + * </p> + * + * @return the Pid of the recipient, or null if the recipient was in fact a + * name. + */ + public Object getRecipient() { + if (toName != null) { + return toName; + } + return to; + } + + /** + * <p> + * Get the Pid of the sender of this message. + * </p> + * + * <p> + * For messages sent to names, the Pid of the sender is included with the + * message. The sender Pid is also available for link, unlink and exit + * messages. It is not available for sendTag messages sent to Pids. + * </p> + * + * @return the Pid of the sender, or null if it was not available. + */ + public OtpErlangPid getSenderPid() { + return from; + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpNode.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpNode.java new file mode 100644 index 0000000000..d499fae3fb --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpNode.java @@ -0,0 +1,807 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; + +/** + * <p> + * Represents a local OTP node. This class is used when you do not wish to + * manage connections yourself - outgoing connections are established as needed, + * and incoming connections accepted automatically. This class supports the use + * of a mailbox API for communication, while management of the underlying + * communication mechanism is automatic and hidden from the application + * programmer. + * </p> + * + * <p> + * Once an instance of this class has been created, obtain one or more mailboxes + * in order to send or receive messages. The first message sent to a given node + * will cause a connection to be set up to that node. Any messages received will + * be delivered to the appropriate mailboxes. + * </p> + * + * <p> + * To shut down the node, call {@link #close close()}. This will prevent the + * node from accepting additional connections and it will cause all existing + * connections to be closed. Any unread messages in existing mailboxes can still + * be read, however no new messages will be delivered to the mailboxes. + * </p> + * + * <p> + * Note that the use of this class requires that Epmd (Erlang Port Mapper + * Daemon) is running on each cooperating host. This class does not start Epmd + * automatically as Erlang does, you must start it manually or through some + * other means. See the Erlang documentation for more information about this. + * </p> + */ +public class OtpNode extends OtpLocalNode { + private boolean initDone = false; + + // thread to manage incoming connections + private Acceptor acceptor = null; + + // keep track of all connections + Hashtable<String, OtpCookedConnection> connections = null; + + // keep track of all mailboxes + Mailboxes mboxes = null; + + // handle status changes + OtpNodeStatus handler; + + // flags + private int flags = 0; + + /** + * <p> + * Create a node using the default cookie. The default cookie is found by + * reading the first line of the .erlang.cookie file in the user's home + * directory. The home directory is obtained from the System property + * "user.home". + * </p> + * + * <p> + * If the file does not exist, an empty string is used. This method makes no + * attempt to create the file. + * </p> + * + * @param node + * the name of this node. + * + * @exception IOException + * if communication could not be initialized. + * + */ + public OtpNode(final String node) throws IOException { + this(node, defaultCookie, 0); + } + + /** + * Create a node. + * + * @param node + * the name of this node. + * + * @param cookie + * the authorization cookie that will be used by this node when + * it communicates with other nodes. + * + * @exception IOException + * if communication could not be initialized. + * + */ + public OtpNode(final String node, final String cookie) throws IOException { + this(node, cookie, 0); + } + + /** + * Create a node. + * + * @param node + * the name of this node. + * + * @param cookie + * the authorization cookie that will be used by this node when + * it communicates with other nodes. + * + * @param port + * the port number you wish to use for incoming connections. + * Specifying 0 lets the system choose an available port. + * + * @exception IOException + * if communication could not be initialized. + * + */ + public OtpNode(final String node, final String cookie, final int port) + throws IOException { + super(node, cookie); + + init(port); + } + + private synchronized void init(final int port) throws IOException { + if (!initDone) { + connections = new Hashtable<String, OtpCookedConnection>(17, + (float) 0.95); + mboxes = new Mailboxes(); + acceptor = new Acceptor(port); + initDone = true; + } + } + + /** + * Close the node. Unpublish the node from Epmd (preventing new connections) + * and close all existing connections. + */ + public synchronized void close() { + acceptor.quit(); + OtpCookedConnection conn; + final Collection<OtpCookedConnection> coll = connections.values(); + final Iterator<OtpCookedConnection> it = coll.iterator(); + + mboxes.clear(); + + while (it.hasNext()) { + conn = it.next(); + it.remove(); + conn.close(); + } + initDone = false; + } + + @Override + protected void finalize() { + close(); + } + + /** + * Create an unnamed {@link OtpMbox mailbox} that can be used to send and + * receive messages with other, similar mailboxes and with Erlang processes. + * Messages can be sent to this mailbox by using its associated + * {@link OtpMbox#self pid}. + * + * @return a mailbox. + */ + public OtpMbox createMbox() { + return mboxes.create(); + } + + /** + * Close the specified mailbox with reason 'normal'. + * + * @param mbox + * the mailbox to close. + * + * <p> + * After this operation, the mailbox will no longer be able to + * receive messages. Any delivered but as yet unretrieved + * messages can still be retrieved however. + * </p> + * + * <p> + * If there are links from the mailbox to other + * {@link OtpErlangPid pids}, they will be broken when this + * method is called and exit signals with reason 'normal' will be + * sent. + * </p> + * + */ + public void closeMbox(final OtpMbox mbox) { + closeMbox(mbox, new OtpErlangAtom("normal")); + } + + /** + * Close the specified mailbox with the given reason. + * + * @param mbox + * the mailbox to close. + * @param reason + * an Erlang term describing the reason for the termination. + * + * <p> + * After this operation, the mailbox will no longer be able to + * receive messages. Any delivered but as yet unretrieved + * messages can still be retrieved however. + * </p> + * + * <p> + * If there are links from the mailbox to other + * {@link OtpErlangPid pids}, they will be broken when this + * method is called and exit signals with the given reason will + * be sent. + * </p> + * + */ + public void closeMbox(final OtpMbox mbox, final OtpErlangObject reason) { + if (mbox != null) { + mboxes.remove(mbox); + mbox.name = null; + mbox.breakLinks(reason); + } + } + + /** + * Create an named mailbox that can be used to send and receive messages + * with other, similar mailboxes and with Erlang processes. Messages can be + * sent to this mailbox by using its registered name or the associated + * {@link OtpMbox#self pid}. + * + * @param name + * a name to register for this mailbox. The name must be unique + * within this OtpNode. + * + * @return a mailbox, or null if the name was already in use. + * + */ + public OtpMbox createMbox(final String name) { + return mboxes.create(name); + } + + /** + * <p> + * Register or remove a name for the given mailbox. Registering a name for a + * mailbox enables others to send messages without knowing the + * {@link OtpErlangPid pid} of the mailbox. A mailbox can have at most one + * name; if the mailbox already had a name, calling this method will + * supercede that name. + * </p> + * + * @param name + * the name to register for the mailbox. Specify null to + * unregister the existing name from this mailbox. + * + * @param mbox + * the mailbox to associate with the name. + * + * @return true if the name was available, or false otherwise. + */ + public boolean registerName(final String name, final OtpMbox mbox) { + return mboxes.register(name, mbox); + } + + /** + * Get a list of all known registered names on this node. + * + * @return an array of Strings, containins all known registered names on + * this node. + */ + + public String[] getNames() { + return mboxes.names(); + } + + /** + * Determine the {@link OtpErlangPid pid} corresponding to a registered name + * on this node. + * + * @return the {@link OtpErlangPid pid} corresponding to the registered + * name, or null if the name is not known on this node. + */ + public OtpErlangPid whereis(final String name) { + final OtpMbox m = mboxes.get(name); + if (m != null) { + return m.self(); + } + return null; + } + + /** + * Register interest in certain system events. The {@link OtpNodeStatus + * OtpNodeStatus} handler object contains callback methods, that will be + * called when certain events occur. + * + * @param handler + * the callback object to register. To clear the handler, specify + * null as the handler to use. + * + */ + public synchronized void registerStatusHandler(final OtpNodeStatus handler) { + this.handler = handler; + } + + /** + * <p> + * Determine if another node is alive. This method has the side effect of + * setting up a connection to the remote node (if possible). Only a single + * outgoing message is sent; the timeout is how long to wait for a response. + * </p> + * + * <p> + * Only a single attempt is made to connect to the remote node, so for + * example it is not possible to specify an extremely long timeout and + * expect to be notified when the node eventually comes up. If you wish to + * wait for a remote node to be started, the following construction may be + * useful: + * </p> + * + * <pre> + * // ping every 2 seconds until positive response + * while (!me.ping(him, 2000)) + * ; + * </pre> + * + * @param node + * the name of the node to ping. + * + * @param timeout + * the time, in milliseconds, to wait for response before + * returning false. + * + * @return true if the node was alive and the correct ping response was + * returned. false if the correct response was not returned on time. + */ + /* + * internal info about the message formats... + * + * the request: -> REG_SEND {6,#Pid<[email protected]>,'',net_kernel} + * {'$gen_call',{#Pid<[email protected]>,#Ref<[email protected]>},{is_auth,bingo@aule}} + * + * the reply: <- SEND {2,'',#Pid<[email protected]>} {#Ref<[email protected]>,yes} + */ + public boolean ping(final String node, final long timeout) { + if (node.equals(this.node)) { + return true; + } else if (node.indexOf('@', 0) < 0 + && node.equals(this.node + .substring(0, this.node.indexOf('@', 0)))) { + return true; + } + + // other node + OtpMbox mbox = null; + try { + mbox = createMbox(); + mbox.send("net_kernel", node, getPingTuple(mbox)); + final OtpErlangObject reply = mbox.receive(timeout); + + final OtpErlangTuple t = (OtpErlangTuple) reply; + final OtpErlangAtom a = (OtpErlangAtom) t.elementAt(1); + return "yes".equals(a.atomValue()); + } catch (final Exception e) { + } finally { + closeMbox(mbox); + } + return false; + } + + /* create the outgoing ping message */ + private OtpErlangTuple getPingTuple(final OtpMbox mbox) { + final OtpErlangObject[] ping = new OtpErlangObject[3]; + final OtpErlangObject[] pid = new OtpErlangObject[2]; + final OtpErlangObject[] node = new OtpErlangObject[2]; + + pid[0] = mbox.self(); + pid[1] = createRef(); + + node[0] = new OtpErlangAtom("is_auth"); + node[1] = new OtpErlangAtom(node()); + + ping[0] = new OtpErlangAtom("$gen_call"); + ping[1] = new OtpErlangTuple(pid); + ping[2] = new OtpErlangTuple(node); + + return new OtpErlangTuple(ping); + } + + /* + * this method simulates net_kernel only for the purpose of replying to + * pings. + */ + private boolean netKernel(final OtpMsg m) { + OtpMbox mbox = null; + try { + final OtpErlangTuple t = (OtpErlangTuple) m.getMsg(); + final OtpErlangTuple req = (OtpErlangTuple) t.elementAt(1); // actual + // request + + final OtpErlangPid pid = (OtpErlangPid) req.elementAt(0); // originating + // pid + + final OtpErlangObject[] pong = new OtpErlangObject[2]; + pong[0] = req.elementAt(1); // his #Ref + pong[1] = new OtpErlangAtom("yes"); + + mbox = createMbox(); + mbox.send(pid, new OtpErlangTuple(pong)); + return true; + } catch (final Exception e) { + } finally { + closeMbox(mbox); + } + return false; + } + + /* + * OtpCookedConnection delivers messages here return true if message was + * delivered successfully, or false otherwise. + */ + boolean deliver(final OtpMsg m) { + OtpMbox mbox = null; + + try { + final int t = m.type(); + + if (t == OtpMsg.regSendTag) { + final String name = m.getRecipientName(); + /* special case for netKernel requests */ + if (name.equals("net_kernel")) { + return netKernel(m); + } else { + mbox = mboxes.get(name); + } + } else { + mbox = mboxes.get(m.getRecipientPid()); + } + + if (mbox == null) { + return false; + } + mbox.deliver(m); + } catch (final Exception e) { + return false; + } + + return true; + } + + /* + * OtpCookedConnection delivers errors here, we send them on to the handler + * specified by the application + */ + void deliverError(final OtpCookedConnection conn, final Exception e) { + removeConnection(conn); + remoteStatus(conn.name, false, e); + } + + /* + * find or create a connection to the given node + */ + OtpCookedConnection getConnection(final String node) { + OtpPeer peer = null; + OtpCookedConnection conn = null; + + synchronized (connections) { + // first just try looking up the name as-is + conn = connections.get(node); + + if (conn == null) { + // in case node had no '@' add localhost info and try again + peer = new OtpPeer(node); + conn = connections.get(peer.node()); + + if (conn == null) { + try { + conn = new OtpCookedConnection(this, peer); + conn.setFlags(flags); + addConnection(conn); + } catch (final Exception e) { + /* false = outgoing */ + connAttempt(peer.node(), false, e); + } + } + } + return conn; + } + } + + void addConnection(final OtpCookedConnection conn) { + if (conn != null && conn.name != null) { + connections.put(conn.name, conn); + remoteStatus(conn.name, true, null); + } + } + + private void removeConnection(final OtpCookedConnection conn) { + if (conn != null && conn.name != null) { + connections.remove(conn.name); + } + } + + /* use these wrappers to call handler functions */ + private synchronized void remoteStatus(final String node, final boolean up, + final Object info) { + if (handler == null) { + return; + } + try { + handler.remoteStatus(node, up, info); + } catch (final Exception e) { + } + } + + synchronized void localStatus(final String node, final boolean up, + final Object info) { + if (handler == null) { + return; + } + try { + handler.localStatus(node, up, info); + } catch (final Exception e) { + } + } + + synchronized void connAttempt(final String node, final boolean incoming, + final Object info) { + if (handler == null) { + return; + } + try { + handler.connAttempt(node, incoming, info); + } catch (final Exception e) { + } + } + + /* + * this class used to wrap the mailbox hashtables so we can use weak + * references + */ + public class Mailboxes { + // mbox pids here + private Hashtable<OtpErlangPid, WeakReference<OtpMbox>> byPid = null; + // mbox names here + private Hashtable<String, WeakReference<OtpMbox>> byName = null; + + public Mailboxes() { + byPid = new Hashtable<OtpErlangPid, WeakReference<OtpMbox>>(17, + (float) 0.95); + byName = new Hashtable<String, WeakReference<OtpMbox>>(17, + (float) 0.95); + } + + public OtpMbox create(final String name) { + OtpMbox m = null; + + synchronized (byName) { + if (get(name) != null) { + return null; + } + final OtpErlangPid pid = createPid(); + m = new OtpMbox(OtpNode.this, pid, name); + byPid.put(pid, new WeakReference<OtpMbox>(m)); + byName.put(name, new WeakReference<OtpMbox>(m)); + } + return m; + } + + public OtpMbox create() { + final OtpErlangPid pid = createPid(); + final OtpMbox m = new OtpMbox(OtpNode.this, pid); + byPid.put(pid, new WeakReference<OtpMbox>(m)); + return m; + } + + public void clear() { + byPid.clear(); + byName.clear(); + } + + public String[] names() { + String allnames[] = null; + + synchronized (byName) { + final int n = byName.size(); + final Enumeration<String> keys = byName.keys(); + allnames = new String[n]; + + int i = 0; + while (keys.hasMoreElements()) { + allnames[i++] = keys.nextElement(); + } + } + return allnames; + } + + public boolean register(final String name, final OtpMbox mbox) { + if (name == null) { + if (mbox.name != null) { + byName.remove(mbox.name); + mbox.name = null; + } + } else { + synchronized (byName) { + if (get(name) != null) { + return false; + } + byName.put(name, new WeakReference<OtpMbox>(mbox)); + mbox.name = name; + } + } + return true; + } + + /* + * look up a mailbox based on its name. If the mailbox has gone out of + * scope we also remove the reference from the hashtable so we don't + * find it again. + */ + public OtpMbox get(final String name) { + final WeakReference<OtpMbox> wr = byName.get(name); + + if (wr != null) { + final OtpMbox m = wr.get(); + + if (m != null) { + return m; + } + byName.remove(name); + } + return null; + } + + /* + * look up a mailbox based on its pid. If the mailbox has gone out of + * scope we also remove the reference from the hashtable so we don't + * find it again. + */ + public OtpMbox get(final OtpErlangPid pid) { + final WeakReference<OtpMbox> wr = byPid.get(pid); + + if (wr != null) { + final OtpMbox m = wr.get(); + + if (m != null) { + return m; + } + byPid.remove(pid); + } + return null; + } + + public void remove(final OtpMbox mbox) { + byPid.remove(mbox.self); + if (mbox.name != null) { + byName.remove(mbox.name); + } + } + } + + /* + * this thread simply listens for incoming connections + */ + public class Acceptor extends Thread { + private final ServerSocket sock; + private final int port; + private volatile boolean done = false; + + Acceptor(final int port) throws IOException { + sock = new ServerSocket(port); + this.port = sock.getLocalPort(); + OtpNode.this.port = this.port; + + setDaemon(true); + setName("acceptor"); + publishPort(); + start(); + } + + private boolean publishPort() throws IOException { + if (getEpmd() != null) { + return false; // already published + } + OtpEpmd.publishPort(OtpNode.this); + return true; + } + + private void unPublishPort() { + // unregister with epmd + OtpEpmd.unPublishPort(OtpNode.this); + + // close the local descriptor (if we have one) + closeSock(epmd); + epmd = null; + } + + public void quit() { + unPublishPort(); + done = true; + closeSock(sock); + localStatus(node, false, null); + } + + private void closeSock(final ServerSocket s) { + try { + if (s != null) { + s.close(); + } + } catch (final Exception e) { + } + } + + private void closeSock(final Socket s) { + try { + if (s != null) { + s.close(); + } + } catch (final Exception e) { + } + } + + public int port() { + return port; + } + + @Override + public void run() { + Socket newsock = null; + OtpCookedConnection conn = null; + + localStatus(node, true, null); + + accept_loop: while (!done) { + conn = null; + + try { + newsock = sock.accept(); + } catch (final Exception e) { + // Problem in java1.2.2: accept throws SocketException + // when socket is closed. This will happen when + // acceptor.quit() + // is called. acceptor.quit() will call localStatus(...), so + // we have to check if that's where we come from. + if (!done) { + localStatus(node, false, e); + } + break accept_loop; + } + + try { + synchronized (connections) { + conn = new OtpCookedConnection(OtpNode.this, newsock); + conn.setFlags(flags); + addConnection(conn); + } + } catch (final OtpAuthException e) { + if (conn != null && conn.name != null) { + connAttempt(conn.name, true, e); + } else { + connAttempt("unknown", true, e); + } + closeSock(newsock); + } catch (final IOException e) { + if (conn != null && conn.name != null) { + connAttempt(conn.name, true, e); + } else { + connAttempt("unknown", true, e); + } + closeSock(newsock); + } catch (final Exception e) { + closeSock(newsock); + closeSock(sock); + localStatus(node, false, e); + break accept_loop; + } + } // while + + // if we have exited loop we must do this too + unPublishPort(); + } + } + + public void setFlags(final int flags) { + this.flags = flags; + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpNodeStatus.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpNodeStatus.java new file mode 100644 index 0000000000..aee1f8b67a --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpNodeStatus.java @@ -0,0 +1,100 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +/** + * <p> + * Provides a callback mechanism for receiving status change information about + * other nodes in the system. Register an instance of this class (or a subclass) + * with your {@link OtpNode OtpNode} when you wish to be notified about such + * status changes and other similar events. + * </p> + * + * <p> + * This class provides default handers that ignore all events. Applications are + * expected to extend this class in order to act on events that are deemed + * interesting. + * </p> + * + * <p> + * <b> Note that this class is likely to change in the near future </b> + * </p> + */ +public class OtpNodeStatus { + public OtpNodeStatus() { + } + + /** + * Notify about remote node status changes. + * + * @param node + * the node whose status change is being indicated by this + * call. + * + * @param up + * true if the node has come up, false if it has gone down. + * + * @param info + * additional info that may be available, for example an + * exception that was raised causing the event in question + * (may be null). + * + */ + public void remoteStatus(final String node, final boolean up, + final Object info) { + } + + /** + * Notify about local node exceptions. + * + * @param node + * the node whose status change is being indicated by this + * call. + * + * @param up + * true if the node has come up, false if it has gone down. + * + * @param info + * additional info that may be available, for example an + * exception that was raised causing the event in question + * (may be null). + */ + public void localStatus(final String node, final boolean up, + final Object info) { + } + + /** + * Notify about failed connection attempts. + * + * @param node + * The name of the remote node + * + * @param incoming + * The direction of the connection attempt, i.e. true for + * incoming, false for outgoing. + * + * @param info + * additional info that may be available, for example an + * exception that was raised causing the event in question + * (may be null). + */ + public void connAttempt(final String node, final boolean incoming, + final Object info) { + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java new file mode 100644 index 0000000000..181350100f --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpOutputStream.java @@ -0,0 +1,816 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +// import java.io.OutputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.DecimalFormat; + +/** + * Provides a stream for encoding Erlang terms to external format, for + * transmission or storage. + * + * <p> + * Note that this class is not synchronized, if you need synchronization you + * must provide it yourself. + * + */ +public class OtpOutputStream extends ByteArrayOutputStream { + /** The default initial size of the stream. * */ + public static final int defaultInitialSize = 2048; + + /** The default increment used when growing the stream. * */ + public static final int defaultIncrement = 2048; + + // static formats, used to encode floats and doubles + private static final DecimalFormat eform = new DecimalFormat("e+00;e-00"); + private static final BigDecimal ten = new BigDecimal(10.0); + private static final BigDecimal one = new BigDecimal(1.0); + + /** + * Create a stream with the default initial size (2048 bytes). + */ + public OtpOutputStream() { + this(defaultInitialSize); + } + + /** + * Create a stream with the specified initial size. + */ + public OtpOutputStream(final int size) { + super(size); + } + + /** + * Create a stream containing the encoded version of the given Erlang term. + */ + public OtpOutputStream(final OtpErlangObject o) { + this(); + write_any(o); + } + + // package scope + /* + * Get the contents of the output stream as an input stream instead. This is + * used internally in {@link OtpCconnection} for tracing outgoing packages. + * + * @param offset where in the output stream to read data from when creating + * the input stream. The offset is necessary because header contents start 5 + * bytes into the header buffer, whereas payload contents start at the + * beginning + * + * @return an input stream containing the same raw data. + */ + OtpInputStream getOtpInputStream(final int offset) { + return new OtpInputStream(super.buf, offset, super.count - offset, 0); + } + + /** + * Get the current position in the stream. + * + * @return the current position in the stream. + */ + public int getPos() { + return super.count; + } + + /** + * Write one byte to the stream. + * + * @param b + * the byte to write. + * + */ + public void write(final byte b) { + if (super.count >= super.buf.length) { + // System.err.println("Expanding buffer from " + this.buf.length + // + " to " + (this.buf.length+defaultIncrement)); + final byte[] tmp = new byte[super.buf.length + defaultIncrement]; + System.arraycopy(super.buf, 0, tmp, 0, super.count); + super.buf = tmp; + } + super.buf[super.count++] = b; + } + + /** + * Write an array of bytes to the stream. + * + * @param buf + * the array of bytes to write. + * + */ + + @Override + public void write(final byte[] buf) { + if (super.count + buf.length > super.buf.length) { + // System.err.println("Expanding buffer from " + super.buf.length + // + " to " + (buf.length + super.buf.lengt + defaultIncrement)); + final byte[] tmp = new byte[super.buf.length + buf.length + + defaultIncrement]; + System.arraycopy(super.buf, 0, tmp, 0, super.count); + super.buf = tmp; + } + System.arraycopy(buf, 0, super.buf, super.count, buf.length); + super.count += buf.length; + } + + /** + * Write the low byte of a value to the stream. + * + * @param n + * the value to use. + * + */ + public void write1(final long n) { + write((byte) (n & 0xff)); + } + + /** + * Write an array of bytes to the stream. + * + * @param buf + * the array of bytes to write. + * + */ + public void writeN(final byte[] bytes) { + write(bytes); + } + + /** + * Get the current capacity of the stream. As bytes are added the capacity + * of the stream is increased automatically, however this method returns the + * current size. + * + * @return the size of the internal buffer used by the stream. + */ + public int length() { + return super.buf.length; + } + + /** + * Get the number of bytes in the stream. + * + * @return the number of bytes in the stream. + * + * @deprecated As of Jinterface 1.4, replaced by super.size(). + * @see #size() + */ + + @Deprecated + public int count() { + return count; + } + + /** + * Write the low two bytes of a value to the stream in big endian order. + * + * @param n + * the value to use. + */ + public void write2BE(final long n) { + write((byte) ((n & 0xff00) >> 8)); + write((byte) (n & 0xff)); + } + + /** + * Write the low four bytes of a value to the stream in big endian order. + * + * @param n + * the value to use. + */ + public void write4BE(final long n) { + write((byte) ((n & 0xff000000) >> 24)); + write((byte) ((n & 0xff0000) >> 16)); + write((byte) ((n & 0xff00) >> 8)); + write((byte) (n & 0xff)); + } + + /** + * Write the low eight (all) bytes of a value to the stream in big endian + * order. + * + * @param n + * the value to use. + */ + public void write8BE(final long n) { + write((byte) (n >> 56 & 0xff)); + write((byte) (n >> 48 & 0xff)); + write((byte) (n >> 40 & 0xff)); + write((byte) (n >> 32 & 0xff)); + write((byte) (n >> 24 & 0xff)); + write((byte) (n >> 16 & 0xff)); + write((byte) (n >> 8 & 0xff)); + write((byte) (n & 0xff)); + } + + /** + * Write any number of bytes in little endian format. + * + * @param n + * the value to use. + * @param b + * the number of bytes to write from the little end. + */ + public void writeLE(long n, final int b) { + for (int i = 0; i < b; i++) { + write((byte) (n & 0xff)); + n >>= 8; + } + } + + /** + * Write the low two bytes of a value to the stream in little endian order. + * + * @param n + * the value to use. + */ + public void write2LE(final long n) { + write((byte) (n & 0xff)); + write((byte) ((n & 0xff00) >> 8)); + } + + /** + * Write the low four bytes of a value to the stream in little endian order. + * + * @param n + * the value to use. + */ + public void write4LE(final long n) { + write((byte) (n & 0xff)); + write((byte) ((n & 0xff00) >> 8)); + write((byte) ((n & 0xff0000) >> 16)); + write((byte) ((n & 0xff000000) >> 24)); + } + + /** + * Write the low eight bytes of a value to the stream in little endian + * order. + * + * @param n + * the value to use. + */ + public void write8LE(final long n) { + write((byte) (n & 0xff)); + write((byte) (n >> 8 & 0xff)); + write((byte) (n >> 16 & 0xff)); + write((byte) (n >> 24 & 0xff)); + write((byte) (n >> 32 & 0xff)); + write((byte) (n >> 40 & 0xff)); + write((byte) (n >> 48 & 0xff)); + write((byte) (n >> 56 & 0xff)); + } + + /** + * Write the low four bytes of a value to the stream in bif endian order, at + * the specified position. If the position specified is beyond the end of + * the stream, this method will have no effect. + * + * Normally this method should be used in conjunction with {@link #size() + * size()}, when is is necessary to insert data into the stream before it is + * known what the actual value should be. For example: + * + * <pre> + * int pos = s.size(); + * s.write4BE(0); // make space for length data, + * // but final value is not yet known + * [ ...more write statements...] + * // later... when we know the length value + * s.poke4BE(pos, length); + * </pre> + * + * + * @param offset + * the position in the stream. + * @param n + * the value to use. + */ + public void poke4BE(final int offset, final long n) { + if (offset < super.count) { + buf[offset + 0] = (byte) ((n & 0xff000000) >> 24); + buf[offset + 1] = (byte) ((n & 0xff0000) >> 16); + buf[offset + 2] = (byte) ((n & 0xff00) >> 8); + buf[offset + 3] = (byte) (n & 0xff); + } + } + + /** + * Write a string to the stream as an Erlang atom. + * + * @param atom + * the string to write. + */ + public void write_atom(final String atom) { + write1(OtpExternal.atomTag); + write2BE(atom.length()); + writeN(atom.getBytes()); + } + + /** + * Write an array of bytes to the stream as an Erlang binary. + * + * @param bin + * the array of bytes to write. + */ + public void write_binary(final byte[] bin) { + write1(OtpExternal.binTag); + write4BE(bin.length); + writeN(bin); + } + + /** + * Write an array of bytes to the stream as an Erlang bitstr. + * + * @param bin + * the array of bytes to write. + * @param pad_bits + * the number of zero pad bits at the low end of the last byte + */ + public void write_bitstr(final byte[] bin, final int pad_bits) { + if (pad_bits == 0) { + write_binary(bin); + return; + } + write1(OtpExternal.bitBinTag); + write4BE(bin.length); + write1(8 - pad_bits); + writeN(bin); + } + + /** + * Write a boolean value to the stream as the Erlang atom 'true' or 'false'. + * + * @param b + * the boolean value to write. + */ + public void write_boolean(final boolean b) { + write_atom(String.valueOf(b)); + } + + /** + * Write a single byte to the stream as an Erlang integer. The byte is + * really an IDL 'octet', that is, unsigned. + * + * @param b + * the byte to use. + */ + public void write_byte(final byte b) { + this.write_long(b & 0xffL, true); + } + + /** + * Write a character to the stream as an Erlang integer. The character may + * be a 16 bit character, kind of IDL 'wchar'. It is up to the Erlang side + * to take care of souch, if they should be used. + * + * @param c + * the character to use. + */ + public void write_char(final char c) { + this.write_long(c & 0xffffL, true); + } + + /** + * Write a double value to the stream. + * + * @param d + * the double to use. + */ + public void write_double(final double d) { + write1(OtpExternal.newFloatTag); + write8BE(Double.doubleToLongBits(d)); + } + + /** + * Write a float value to the stream. + * + * @param f + * the float to use. + */ + public void write_float(final float f) { + write_double(f); + } + + public void write_big_integer(BigInteger v) { + if (v.bitLength() < 64) { + this.write_long(v.longValue(), true); + return; + } + final int signum = v.signum(); + if (signum < 0) { + v = v.negate(); + } + final byte[] magnitude = v.toByteArray(); + final int n = magnitude.length; + // Reverse the array to make it little endian. + for (int i = 0, j = n; i < j--; i++) { + // Swap [i] with [j] + final byte b = magnitude[i]; + magnitude[i] = magnitude[j]; + magnitude[j] = b; + } + if ((n & 0xFF) == n) { + write1(OtpExternal.smallBigTag); + write1(n); // length + } else { + write1(OtpExternal.largeBigTag); + write4BE(n); // length + } + write1(signum < 0 ? 1 : 0); // sign + // Write the array + writeN(magnitude); + } + + void write_long(final long v, final boolean unsigned) { + /* + * If v<0 and unsigned==true the value + * java.lang.Long.MAX_VALUE-java.lang.Long.MIN_VALUE+1+v is written, i.e + * v is regarded as unsigned two's complement. + */ + if ((v & 0xffL) == v) { + // will fit in one byte + write1(OtpExternal.smallIntTag); + write1(v); + } else { + // note that v != 0L + if (v < 0 && unsigned || v < OtpExternal.erlMin + || v > OtpExternal.erlMax) { + // some kind of bignum + final long abs = unsigned ? v : v < 0 ? -v : v; + final int sign = unsigned ? 0 : v < 0 ? 1 : 0; + int n; + long mask; + for (mask = 0xFFFFffffL, n = 4; (abs & mask) != abs; n++, mask = mask << 8 | 0xffL) { + ; // count nonzero bytes + } + write1(OtpExternal.smallBigTag); + write1(n); // length + write1(sign); // sign + writeLE(abs, n); // value. obs! little endian + } else { + write1(OtpExternal.intTag); + write4BE(v); + } + } + } + + /** + * Write a long to the stream. + * + * @param l + * the long to use. + */ + public void write_long(final long l) { + this.write_long(l, false); + } + + /** + * Write a positive long to the stream. The long is interpreted as a two's + * complement unsigned long even if it is negative. + * + * @param ul + * the long to use. + */ + public void write_ulong(final long ul) { + this.write_long(ul, true); + } + + /** + * Write an integer to the stream. + * + * @param i + * the integer to use. + */ + public void write_int(final int i) { + this.write_long(i, false); + } + + /** + * Write a positive integer to the stream. The integer is interpreted as a + * two's complement unsigned integer even if it is negative. + * + * @param ui + * the integer to use. + */ + public void write_uint(final int ui) { + this.write_long(ui & 0xFFFFffffL, true); + } + + /** + * Write a short to the stream. + * + * @param s + * the short to use. + */ + public void write_short(final short s) { + this.write_long(s, false); + } + + /** + * Write a positive short to the stream. The short is interpreted as a two's + * complement unsigned short even if it is negative. + * + * @param s + * the short to use. + */ + public void write_ushort(final short us) { + this.write_long(us & 0xffffL, true); + } + + /** + * Write an Erlang list header to the stream. After calling this method, you + * must write 'arity' elements to the stream followed by nil, or it will not + * be possible to decode it later. + * + * @param arity + * the number of elements in the list. + */ + public void write_list_head(final int arity) { + if (arity == 0) { + write_nil(); + } else { + write1(OtpExternal.listTag); + write4BE(arity); + } + } + + /** + * Write an empty Erlang list to the stream. + */ + public void write_nil() { + write1(OtpExternal.nilTag); + } + + /** + * Write an Erlang tuple header to the stream. After calling this method, + * you must write 'arity' elements to the stream or it will not be possible + * to decode it later. + * + * @param arity + * the number of elements in the tuple. + */ + public void write_tuple_head(final int arity) { + if (arity < 0xff) { + write1(OtpExternal.smallTupleTag); + write1(arity); + } else { + write1(OtpExternal.largeTupleTag); + write4BE(arity); + } + } + + /** + * Write an Erlang PID to the stream. + * + * @param node + * the nodename. + * + * @param id + * an arbitrary number. Only the low order 15 bits will be used. + * + * @param serial + * another arbitrary number. Only the low order 13 bits will be + * used. + * + * @param creation + * yet another arbitrary number. Only the low order 2 bits will + * be used. + * + */ + public void write_pid(final String node, final int id, final int serial, + final int creation) { + write1(OtpExternal.pidTag); + write_atom(node); + write4BE(id & 0x7fff); // 15 bits + write4BE(serial & 0x1fff); // 13 bits + write1(creation & 0x3); // 2 bits + } + + /** + * Write an Erlang port to the stream. + * + * @param node + * the nodename. + * + * @param id + * an arbitrary number. Only the low order 28 bits will be used. + * + * @param creation + * another arbitrary number. Only the low order 2 bits will be + * used. + * + */ + public void write_port(final String node, final int id, final int creation) { + write1(OtpExternal.portTag); + write_atom(node); + write4BE(id & 0xfffffff); // 28 bits + write1(creation & 0x3); // 2 bits + } + + /** + * Write an old style Erlang ref to the stream. + * + * @param node + * the nodename. + * + * @param id + * an arbitrary number. Only the low order 18 bits will be used. + * + * @param creation + * another arbitrary number. Only the low order 2 bits will be + * used. + * + */ + public void write_ref(final String node, final int id, final int creation) { + write1(OtpExternal.refTag); + write_atom(node); + write4BE(id & 0x3ffff); // 18 bits + write1(creation & 0x3); // 2 bits + } + + /** + * Write a new style (R6 and later) Erlang ref to the stream. + * + * @param node + * the nodename. + * + * @param ids + * an array of arbitrary numbers. Only the low order 18 bits of + * the first number will be used. If the array contains only one + * number, an old style ref will be written instead. At most + * three numbers will be read from the array. + * + * @param creation + * another arbitrary number. Only the low order 2 bits will be + * used. + * + */ + public void write_ref(final String node, final int[] ids, final int creation) { + int arity = ids.length; + if (arity > 3) { + arity = 3; // max 3 words in ref + } + + if (arity == 1) { + // use old method + this.write_ref(node, ids[0], creation); + } else { + // r6 ref + write1(OtpExternal.newRefTag); + + // how many id values + write2BE(arity); + + write_atom(node); + + // note: creation BEFORE id in r6 ref + write1(creation & 0x3); // 2 bits + + // first int gets truncated to 18 bits + write4BE(ids[0] & 0x3ffff); + + // remaining ones are left as is + for (int i = 1; i < arity; i++) { + write4BE(ids[i]); + } + } + } + + /** + * Write a string to the stream. + * + * @param s + * the string to write. + */ + public void write_string(final String s) { + final int len = s.length(); + + switch (len) { + case 0: + write_nil(); + break; + default: + if (len <= 65535 && is8bitString(s)) { // 8-bit string + try { + final byte[] bytebuf = s.getBytes("ISO-8859-1"); + write1(OtpExternal.stringTag); + write2BE(len); + writeN(bytebuf); + } catch (final UnsupportedEncodingException e) { + write_nil(); // it should never ever get here... + } + } else { // unicode or longer, must code as list + final char[] charbuf = s.toCharArray(); + final int[] codePoints = OtpErlangString.stringToCodePoints(s); + write_list_head(codePoints.length); + for (final int codePoint : codePoints) { + write_int(codePoint); + } + write_nil(); + } + } + } + + private boolean is8bitString(final String s) { + for (int i = 0; i < s.length(); ++i) { + final char c = s.charAt(i); + if (c < 0 || c > 255) { + return false; + } + } + return true; + } + + /** + * Write an arbitrary Erlang term to the stream in compressed format. + * + * @param o + * the Erlang tem to write. + */ + public void write_compressed(final OtpErlangObject o) { + final OtpOutputStream oos = new OtpOutputStream(o); + write1(OtpExternal.compressedTag); + write4BE(oos.size()); + final java.io.FilterOutputStream fos = new java.io.FilterOutputStream( + this); + final java.util.zip.DeflaterOutputStream dos = new java.util.zip.DeflaterOutputStream( + fos); + try { + oos.writeTo(dos); + dos.close(); + } catch (final IOException e) { + throw new java.lang.IllegalArgumentException( + "Intremediate stream failed for Erlang object " + o); + } + } + + /** + * Write an arbitrary Erlang term to the stream. + * + * @param o + * the Erlang term to write. + */ + public void write_any(final OtpErlangObject o) { + // calls one of the above functions, depending on o + o.encode(this); + } + + public void write_fun(final OtpErlangPid pid, final String module, + final long old_index, final int arity, final byte[] md5, + final long index, final long uniq, final OtpErlangObject[] freeVars) { + if (arity == -1) { + write1(OtpExternal.funTag); + write4BE(freeVars.length); + pid.encode(this); + write_atom(module); + write_long(index); + write_long(uniq); + for (final OtpErlangObject fv : freeVars) { + fv.encode(this); + } + } else { + write1(OtpExternal.newFunTag); + final int saveSizePos = getPos(); + write4BE(0); // this is where we patch in the size + write1(arity); + writeN(md5); + write4BE(index); + write4BE(freeVars.length); + write_atom(module); + write_long(old_index); + write_long(uniq); + pid.encode(this); + for (final OtpErlangObject fv : freeVars) { + fv.encode(this); + } + poke4BE(saveSizePos, getPos() - saveSizePos); + } + } + + public void write_external_fun(final String module, final String function, + final int arity) { + write1(OtpExternal.externalFunTag); + write_atom(module); + write_atom(function); + write_long(arity); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpPeer.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpPeer.java new file mode 100644 index 0000000000..df5ce61820 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpPeer.java @@ -0,0 +1,86 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.IOException; +import java.net.UnknownHostException; + +/** + * Represents a remote OTP node. It acts only as a container for the nodename + * and other node-specific information that is needed by the + * {@link OtpConnection} class. + */ +public class OtpPeer extends AbstractNode { + int distChoose = 0; /* + * this is set by OtpConnection and is the highest + * common protocol version we both support + */ + + OtpPeer() { + super(); + } + + /** + * Create a peer node. + * + * @param node + * the name of the node. + */ + public OtpPeer(final String node) { + super(node); + } + + /** + * Create a connection to a remote node. + * + * @param self + * the local node from which you wish to connect. + * + * @return a connection to the remote node. + * + * @exception java.net.UnknownHostException + * if the remote host could not be found. + * + * @exception java.io.IOException + * if it was not possible to connect to the remote node. + * + * @exception OtpAuthException + * if the connection was refused by the remote node. + * + * @deprecated Use the corresponding method in {@link OtpSelf} instead. + */ + @Deprecated + public OtpConnection connect(final OtpSelf self) throws IOException, + UnknownHostException, OtpAuthException { + return new OtpConnection(self, this); + } + + // package + /* + * Get the port number used by the remote node. + * + * @return the port number used by the remote node, or 0 if the node was not + * registered with the port mapper. + * + * @exception java.io.IOException if the port mapper could not be contacted. + */ + int port() throws IOException { + return OtpEpmd.lookupPort(this); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSelf.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSelf.java new file mode 100644 index 0000000000..8e78cda894 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSelf.java @@ -0,0 +1,221 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; + +/** + * Represents an OTP node. It is used to connect to remote nodes or accept + * incoming connections from remote nodes. + * + * <p> + * When the Java node will be connecting to a remote Erlang, Java or C node, it + * must first identify itself as a node by creating an instance of this class, + * after which it may connect to the remote node. + * + * <p> + * When you create an instance of this class, it will bind a socket to a port so + * that incoming connections can be accepted. However the port number will not + * be made available to other nodes wishing to connect until you explicitely + * register with the port mapper daemon by calling {@link #publishPort()}. + * </p> + * + * <pre> + * OtpSelf self = new OtpSelf("client", "authcookie"); // identify self + * OtpPeer other = new OtpPeer("server"); // identify peer + * + * OtpConnection conn = self.connect(other); // connect to peer + * </pre> + * + */ +public class OtpSelf extends OtpLocalNode { + private final ServerSocket sock; + private final OtpErlangPid pid; + + /** + * <p> + * Create a self node using the default cookie. The default cookie is found + * by reading the first line of the .erlang.cookie file in the user's home + * directory. The home directory is obtained from the System property + * "user.home". + * </p> + * + * <p> + * If the file does not exist, an empty string is used. This method makes no + * attempt to create the file. + * </p> + * + * @param node + * the name of this node. + * + */ + public OtpSelf(final String node) throws IOException { + this(node, defaultCookie, 0); + } + + /** + * Create a self node. + * + * @param node + * the name of this node. + * + * @param cookie + * the authorization cookie that will be used by this node + * when it communicates with other nodes. + */ + public OtpSelf(final String node, final String cookie) throws IOException { + this(node, cookie, 0); + } + + public OtpSelf(final String node, final String cookie, final int port) + throws IOException { + super(node, cookie); + + sock = new ServerSocket(port); + + if (port != 0) { + this.port = port; + } else { + this.port = sock.getLocalPort(); + } + + pid = createPid(); + } + + /** + * Get the Erlang PID that will be used as the sender id in all "anonymous" + * messages sent by this node. Anonymous messages are those sent via send + * methods in {@link OtpConnection OtpConnection} that do not specify a + * sender. + * + * @return the Erlang PID that will be used as the sender id in all + * anonymous messages sent by this node. + */ + public OtpErlangPid pid() { + return pid; + } + + /** + * Make public the information needed by remote nodes that may wish to + * connect to this one. This method establishes a connection to the Erlang + * port mapper (Epmd) and registers the server node's name and port so that + * remote nodes are able to connect. + * + * <p> + * This method will fail if an Epmd process is not running on the localhost. + * See the Erlang documentation for information about starting Epmd. + * + * <p> + * Note that once this method has been called, the node is expected to be + * available to accept incoming connections. For that reason you should make + * sure that you call {@link #accept()} shortly after calling + * {@link #publishPort()}. When you no longer intend to accept connections + * you should call {@link #unPublishPort()}. + * + * @return true if the operation was successful, false if the node was + * already registered. + * + * @exception java.io.IOException + * if the port mapper could not be contacted. + */ + public boolean publishPort() throws IOException { + if (getEpmd() != null) { + return false; // already published + } + + OtpEpmd.publishPort(this); + return getEpmd() != null; + } + + /** + * Unregister the server node's name and port number from the Erlang port + * mapper, thus preventing any new connections from remote nodes. + */ + public void unPublishPort() { + // unregister with epmd + OtpEpmd.unPublishPort(this); + + // close the local descriptor (if we have one) + try { + if (super.epmd != null) { + super.epmd.close(); + } + } catch (final IOException e) {/* ignore close errors */ + } + super.epmd = null; + } + + /** + * Accept an incoming connection from a remote node. A call to this method + * will block until an incoming connection is at least attempted. + * + * @return a connection to a remote node. + * + * @exception java.io.IOException + * if a remote node attempted to connect but no common + * protocol was found. + * + * @exception OtpAuthException + * if a remote node attempted to connect, but was not + * authorized to connect. + */ + public OtpConnection accept() throws IOException, OtpAuthException { + Socket newsock = null; + + while (true) { + try { + newsock = sock.accept(); + return new OtpConnection(this, newsock); + } catch (final IOException e) { + try { + if (newsock != null) { + newsock.close(); + } + } catch (final IOException f) {/* ignore close errors */ + } + throw e; + } + } + } + + /** + * Open a connection to a remote node. + * + * @param other + * the remote node to which you wish to connect. + * + * @return a connection to the remote node. + * + * @exception java.net.UnknownHostException + * if the remote host could not be found. + * + * @exception java.io.IOException + * if it was not possible to connect to the remote node. + * + * @exception OtpAuthException + * if the connection was refused by the remote node. + */ + public OtpConnection connect(final OtpPeer other) throws IOException, + UnknownHostException, OtpAuthException { + return new OtpConnection(this, other); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpServer.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpServer.java new file mode 100644 index 0000000000..0de399ac61 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpServer.java @@ -0,0 +1,110 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +import java.io.IOException; + +/** + * Represents a local OTP client or server node. It is used when you want other + * nodes to be able to establish connections to this one. + * + * When you create an instance of this class, it will bind a socket to a port so + * that incoming connections can be accepted. However the port number will not + * be made available to other nodes wishing to connect until you explicitely + * register with the port mapper daemon by calling {@link #publishPort()}. + * + * <p> + * When the Java node will be connecting to a remote Erlang, Java or C node, it + * must first identify itself as a node by creating an instance of this class, + * after which it may connect to the remote node. + * + * <p> + * Setting up a connection may be done as follows: + * + * + * <pre> + * OtpServer self = new OtpServer("server", "cookie"); // identify self + * self.publishPort(); // make port information available + * + * OtpConnection conn = self.accept(); // get incoming connection + * </pre> + * + * @see OtpSelf + * + * @deprecated the functionality of this class has been moved to {@link OtpSelf}. + */ +@Deprecated +public class OtpServer extends OtpSelf { + /** + * Create an {@link OtpServer} from an existing {@link OtpSelf}. + * + * @param self + * an existing self node. + * + * @exception java.io.IOException + * if a ServerSocket could not be created. + * + */ + public OtpServer(final OtpSelf self) throws IOException { + super(self.node(), self.cookie()); + } + + /** + * Create an OtpServer, using a vacant port chosen by the operating system. + * To determine what port was chosen, call the object's {@link #port()} + * method. + * + * @param node + * the name of the node. + * + * @param cookie + * the authorization cookie that will be used by this node + * when accepts connections from remote nodes. + * + * @exception java.io.IOException + * if a ServerSocket could not be created. + * + */ + public OtpServer(final String node, final String cookie) throws IOException { + super(node, cookie); + } + + /** + * Create an OtpServer, using the specified port number. + * + * @param node + * a name for this node, as above. + * + * @param cookie + * the authorization cookie that will be used by this node + * when accepts connections from remote nodes. + * + * @param port + * the port number to bind the socket to. + * + * @exception java.io.IOException + * if a ServerSocket could not be created or if the + * chosen port number was not available. + * + */ + public OtpServer(final String node, final String cookie, final int port) + throws IOException { + super(node, cookie, port); + } +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSystem.java b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSystem.java new file mode 100644 index 0000000000..969da39d70 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpSystem.java @@ -0,0 +1,53 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2004-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +package com.ericsson.otp.erlang; + +final class OtpSystem { + + // Place status variables here + + static { + + final String rel = System.getProperty("OtpCompatRel", "0"); + + try { + + switch (Integer.parseInt(rel)) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 0: + default: + break; + } + } catch (final NumberFormatException e) { + /* Ignore ... */ + } + + } + + // Place query functions here + +} diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/ignore_config_record.inf b/lib/jinterface/java_src/com/ericsson/otp/erlang/ignore_config_record.inf new file mode 100644 index 0000000000..0a5053eba3 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/ignore_config_record.inf @@ -0,0 +1 @@ +This file makes clearmake use the -T switch for this subdirectory diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/java_files b/lib/jinterface/java_src/com/ericsson/otp/erlang/java_files new file mode 100644 index 0000000000..1390542194 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/java_files @@ -0,0 +1,84 @@ +# -*-Makefile-*- + +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2000-2009. All Rights Reserved. +# +# The contents of this file are subject to the Erlang Public License, +# Version 1.1, (the "License"); you may not use this file except in +# compliance with the License. You should have received a copy of the +# Erlang Public License along with this software. If not, it can be +# retrieved online at http://www.erlang.org/. +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +# the License for the specific language governing rights and limitations +# under the License. +# +# %CopyrightEnd% +# + +# this file is included in the doc and src makefiles +# so the list of files only needs to be updated in one place +# i.e. here + +EXCEPTIONS = \ + OtpAuthException \ + OtpErlangDecodeException \ + OtpErlangException \ + OtpErlangExit \ + OtpErlangRangeException \ + OtpException + +COMM = \ + AbstractConnection \ + AbstractNode \ + GenericQueue \ + Link \ + Links \ + OtpConnection \ + OtpCookedConnection \ + OtpEpmd \ + OtpErlangFun \ + OtpErlangExternalFun \ + OtpExternal \ + OtpInputStream \ + OtpLocalNode \ + OtpNodeStatus \ + OtpMD5 \ + OtpMbox \ + OtpMsg \ + OtpNode \ + OtpOutputStream \ + OtpPeer \ + OtpSelf \ + OtpServer + +ERL = \ + OtpErlangAtom \ + OtpErlangBinary \ + OtpErlangBitstr \ + OtpErlangBoolean \ + OtpErlangByte \ + OtpErlangChar \ + OtpErlangDouble \ + OtpErlangFloat \ + OtpErlangInt \ + OtpErlangList \ + OtpErlangLong \ + OtpErlangObject \ + OtpErlangPid \ + OtpErlangPort \ + OtpErlangRef \ + OtpErlangShort\ + OtpErlangString\ + OtpErlangTuple \ + OtpErlangUInt \ + OtpErlangUShort + +MISC = \ + OtpSystem + +JAVA_FILES=$(EXCEPTIONS) $(ERL) $(COMM) $(MISC) + diff --git a/lib/jinterface/java_src/com/ericsson/otp/erlang/package.html b/lib/jinterface/java_src/com/ericsson/otp/erlang/package.html new file mode 100644 index 0000000000..039a8778f2 --- /dev/null +++ b/lib/jinterface/java_src/com/ericsson/otp/erlang/package.html @@ -0,0 +1,157 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<!-- + + %CopyrightBegin% + + Copyright Ericsson AB 2000-2009. All Rights Reserved. + + The contents of this file are subject to the Erlang Public License, + Version 1.1, (the "License"); you may not use this file except in + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + %CopyrightEnd% +--> +<html> +<head> +<!-- + + File: package.html + + Copyright ... + +--> +</head> +<body bgcolor="white"> + +<p> This package provides support for communication with Erlang and +representation of Erlang datatypes. + +<p><em>Note:</em> By default, <code>jinterface</code> is only guaranteed +to be compatible with other Erlang/OTP components from the same release as +<code>jinterface</code> itself. For example, <code>jinterface</code> +from the OTP R10 release is not compatible with an Erlang emulator from +the OTP R9 release by default. <code>jinterface</code> can be set in +compatibility mode of an earlier release (not earlier that R7), though. +The compatibility mode is set by usage of the <code>OtpCompatRel</code> +property. By starting the jvm with the command-line argument +<code>-DOtpCompatRel=9</code>, <code>jinterface</code> will be compatible +with the R9 release of OTP. <em>Warning!</em> You may run into trouble if +this feature is used carelessly. Always make sure that all communicating +components are either from the same Erlang/OTP release, or from release +X and release Y where all components from release Y are in compatibility +mode of release X. + +<p> The classes +{@link com.ericsson.otp.erlang.OtpErlangList}, +{@link com.ericsson.otp.erlang.OtpErlangTuple}, +{@link com.ericsson.otp.erlang.OtpErlangBinary}, +{@link com.ericsson.otp.erlang.OtpErlangAtom}, +{@link com.ericsson.otp.erlang.OtpErlangBoolean}, +{@link com.ericsson.otp.erlang.OtpErlangByte}, +{@link com.ericsson.otp.erlang.OtpErlangChar}, +{@link com.ericsson.otp.erlang.OtpErlangDouble}, +{@link com.ericsson.otp.erlang.OtpErlangFloat}, +{@link com.ericsson.otp.erlang.OtpErlangLong}, +{@link com.ericsson.otp.erlang.OtpErlangInt}, +{@link com.ericsson.otp.erlang.OtpErlangUInt}, +{@link com.ericsson.otp.erlang.OtpErlangShort}, +{@link com.ericsson.otp.erlang.OtpErlangUShort}, +{@link com.ericsson.otp.erlang.OtpErlangString}, +{@link com.ericsson.otp.erlang.OtpErlangObject}, +{@link com.ericsson.otp.erlang.OtpErlangPid}, +{@link com.ericsson.otp.erlang.OtpErlangPort}, +and {@link com.ericsson.otp.erlang.OtpErlangRef} +represent the various Erlang datatypes. + + +<p> There are two basic mechanisms for communicating with Erlang, +described briefly here. Note that the two mechanisms are not intended +to be used together. Which mechanism you choose depends on your +application and the level of control it needs. </p> + +<p> You can use {@link com.ericsson.otp.erlang.OtpNode}, which can +manage incoming and outgoing connections for you. With {@link +com.ericsson.otp.erlang.OtpNode} a thread is automatically started to +listen for incoming connections, make necessary outgoing connections, +and dispatch messages to their recipients. {@link +com.ericsson.otp.erlang.OtpNode} supports the concept of {@link +com.ericsson.otp.erlang.OtpMbox mailboxes}, allowing you to have +several Java components communicating independently with Erlang. +</p> + +<pre> + OtpNode node = new OtpNode("bingo"); + OtpMbox mbox = node.createMbox(); + + mbox.send("foo@localhost",new OtpErlangAtom("hej")); +</pre> + +<p> If you need more control (but less support from the library), you +can manage connections yourself using the {@link +com.ericsson.otp.erlang.OtpSelf} and {@link +com.ericsson.otp.erlang.OtpConnection} classes, in which case you can +control explicitly which connections are made and which messages are +sent. Received messages are not dispatched by {@link +com.ericsson.otp.erlang.OtpConnection}. </p> + +<p> The classes {@link com.ericsson.otp.erlang.OtpPeer}, {@link +com.ericsson.otp.erlang.OtpSelf} and {@link +com.ericsson.otp.erlang.OtpServer} are used to represent OTP nodes and +are neccessary in order to set up communication between the Java +thread and a remote node. Once a connection has been established, it +is represented by an {@link com.ericsson.otp.erlang.OtpConnection}, +through which all communication goes. + +<p> Setting up a connection with a remote node is straightforward. You +create objects representing the local and remote nodes, then call the +local node's {@link +com.ericsson.otp.erlang.OtpSelf#connect(com.ericsson.otp.erlang.OtpPeer) +connect()} method: + +<pre> + OtpSelf self = new OtpSelf("client","cookie"); + OtpPeer other = new OtpPeer("server"); + OtpConnection conn = self.connect(other); +</pre> + +<p>If you wish to be able to accept incoming connections as well as +make outgoing ones, you first must register the listen port with EPMD +(described in the Erlang documentation). Once that is done, you can +accept incoming connections: + +<pre> + OtpServer self = new OtpSelf("server","cookie"); + self.publishPort(); + OtpConnection conn = self.accept(); +</pre> + + +<p>Once the connection is established by one of the above methods ({@link +com.ericsson.otp.erlang.OtpSelf#connect(com.ericsson.otp.erlang.OtpPeer) +connect()} or {@link com.ericsson.otp.erlang.OtpSelf#accept() +accept()}), you can use the resulting {@link +com.ericsson.otp.erlang.OtpConnection OtpConnection} to send and +receive messages: + +<pre> + OtpErlangAtom msg = new ErlangOtpAtom("hello"); + conn.send("echoserver", msg); + + OtpErlangObject reply = conn.receive(); + System.out.println("Received " + reply); +</pre> + +<p> Finally, you can get an even greater level of control (and even +less support from the library) by subclassing {@link +com.ericsson.otp.erlang.AbstractConnection} and implementing the +communication primitives needed by your application. </p> + +</body> +</html> |