Loading README.md +7 −0 Original line number Diff line number Diff line Loading @@ -3,3 +3,10 @@ This project is a copy of Erlang's JInterface library to link Erlang and Java programs, packaged for Maven in order to use it in other projects. Available versions: | JInterface | Erlang versions supported | |------------|---------------------------| | 1.9.1 | >=21.1, <22 | All versions support Java 8 and higher. java_src/com/ericsson/otp/erlang/AbstractConnection.java 0 → 100644 +1391 −0 File added.Preview size limit exceeded, changes collapsed. Show changes java_src/com/ericsson/otp/erlang/AbstractNode.java 0 → 100644 +310 −0 Original line number Diff line number Diff line /* * %CopyrightBegin% * * Copyright Ericsson AB 2000-2016. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions 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 "alive@host.fully.qualified.domainname". 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 implements OtpTransportFactory { static String localHost = null; String node; String host; String alive; String cookie; static String defaultCookie = null; final OtpTransportFactory transportFactory; // 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; static final int dFlagUnicodeIo = 0x1000; static final int dFlagUtf8Atoms = 0x10000; static final int dFlagMapTag = 0x20000; static final int dFlagBigCreation = 0x40000; 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 | dFlagUtf8Atoms | dFlagMapTag | dFlagBigCreation; /* 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 homeDir = getHomeDir(); final String dotCookieFilename = homeDir + File.separator + ".erlang.cookie"; BufferedReader br = null; try { final File dotCookieFile = new File(dotCookieFilename); br = new BufferedReader(new FileReader(dotCookieFile)); final String line = br.readLine(); if (line == null) { defaultCookie = ""; } else { defaultCookie = line.trim(); } } catch (final IOException e) { defaultCookie = ""; } finally { try { if (br != null) { br.close(); } } catch (final IOException e) { } } } protected AbstractNode(final OtpTransportFactory transportFactory) { this.transportFactory = transportFactory; } /** * Create a node with the given name and default cookie and transport * factory. */ protected AbstractNode(final String node) { this(node, defaultCookie, new OtpSocketTransportFactory()); } /** * Create a node with the given name, transport factory and the default * cookie. */ protected AbstractNode(final String node, final OtpTransportFactory transportFactory) { this(node, defaultCookie, transportFactory); } /** * Create a node with the given name, cookie and default transport factory. */ protected AbstractNode(final String name, final String cookie) { this(name, cookie, new OtpSocketTransportFactory()); } /** * Create a node with the given name, cookie and transport factory. */ protected AbstractNode(final String name, final String cookie, final OtpTransportFactory transportFactory) { this.cookie = cookie; this.transportFactory = transportFactory; 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(); } private static String getHomeDir() { final String home = System.getProperty("user.home"); if (System.getProperty("os.name").toLowerCase().contains("windows")) { final String drive = System.getenv("HOMEDRIVE"); final String path = System.getenv("HOMEPATH"); return drive != null && path != null ? drive + path : home; } return home; } public OtpTransport createTransport(final String addr, final int port) throws IOException { return transportFactory.createTransport(addr, port); } public OtpTransport createTransport(final InetAddress addr, final int port) throws IOException { return transportFactory.createTransport(addr, port); } public OtpServerTransport createServerTransport(final int port) throws IOException { return transportFactory.createServerTransport(port); } } java_src/com/ericsson/otp/erlang/GenericQueue.java 0 → 100644 +187 −0 Original line number Diff line number Diff line /* * %CopyrightBegin% * * Copyright Ericsson AB 2000-2016. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions 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; } } } java_src/com/ericsson/otp/erlang/Link.java 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * %CopyrightBegin% * * Copyright Ericsson AB 2000-2016. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions 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 alocal, final OtpErlangPid aremote) { return local.equals(alocal) && remote.equals(aremote) || local.equals(aremote) && remote.equals(alocal); } @Override public int hashCode() { if (hashCodeValue == 0) { final OtpErlangObject.Hash hash = new OtpErlangObject.Hash(5); hash.combine(local.hashCode() + remote.hashCode()); hashCodeValue = hash.valueOf(); } return hashCodeValue; } } Loading
README.md +7 −0 Original line number Diff line number Diff line Loading @@ -3,3 +3,10 @@ This project is a copy of Erlang's JInterface library to link Erlang and Java programs, packaged for Maven in order to use it in other projects. Available versions: | JInterface | Erlang versions supported | |------------|---------------------------| | 1.9.1 | >=21.1, <22 | All versions support Java 8 and higher.
java_src/com/ericsson/otp/erlang/AbstractConnection.java 0 → 100644 +1391 −0 File added.Preview size limit exceeded, changes collapsed. Show changes
java_src/com/ericsson/otp/erlang/AbstractNode.java 0 → 100644 +310 −0 Original line number Diff line number Diff line /* * %CopyrightBegin% * * Copyright Ericsson AB 2000-2016. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions 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 "alive@host.fully.qualified.domainname". 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 implements OtpTransportFactory { static String localHost = null; String node; String host; String alive; String cookie; static String defaultCookie = null; final OtpTransportFactory transportFactory; // 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; static final int dFlagUnicodeIo = 0x1000; static final int dFlagUtf8Atoms = 0x10000; static final int dFlagMapTag = 0x20000; static final int dFlagBigCreation = 0x40000; 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 | dFlagUtf8Atoms | dFlagMapTag | dFlagBigCreation; /* 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 homeDir = getHomeDir(); final String dotCookieFilename = homeDir + File.separator + ".erlang.cookie"; BufferedReader br = null; try { final File dotCookieFile = new File(dotCookieFilename); br = new BufferedReader(new FileReader(dotCookieFile)); final String line = br.readLine(); if (line == null) { defaultCookie = ""; } else { defaultCookie = line.trim(); } } catch (final IOException e) { defaultCookie = ""; } finally { try { if (br != null) { br.close(); } } catch (final IOException e) { } } } protected AbstractNode(final OtpTransportFactory transportFactory) { this.transportFactory = transportFactory; } /** * Create a node with the given name and default cookie and transport * factory. */ protected AbstractNode(final String node) { this(node, defaultCookie, new OtpSocketTransportFactory()); } /** * Create a node with the given name, transport factory and the default * cookie. */ protected AbstractNode(final String node, final OtpTransportFactory transportFactory) { this(node, defaultCookie, transportFactory); } /** * Create a node with the given name, cookie and default transport factory. */ protected AbstractNode(final String name, final String cookie) { this(name, cookie, new OtpSocketTransportFactory()); } /** * Create a node with the given name, cookie and transport factory. */ protected AbstractNode(final String name, final String cookie, final OtpTransportFactory transportFactory) { this.cookie = cookie; this.transportFactory = transportFactory; 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(); } private static String getHomeDir() { final String home = System.getProperty("user.home"); if (System.getProperty("os.name").toLowerCase().contains("windows")) { final String drive = System.getenv("HOMEDRIVE"); final String path = System.getenv("HOMEPATH"); return drive != null && path != null ? drive + path : home; } return home; } public OtpTransport createTransport(final String addr, final int port) throws IOException { return transportFactory.createTransport(addr, port); } public OtpTransport createTransport(final InetAddress addr, final int port) throws IOException { return transportFactory.createTransport(addr, port); } public OtpServerTransport createServerTransport(final int port) throws IOException { return transportFactory.createServerTransport(port); } }
java_src/com/ericsson/otp/erlang/GenericQueue.java 0 → 100644 +187 −0 Original line number Diff line number Diff line /* * %CopyrightBegin% * * Copyright Ericsson AB 2000-2016. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions 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; } } }
java_src/com/ericsson/otp/erlang/Link.java 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * %CopyrightBegin% * * Copyright Ericsson AB 2000-2016. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions 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 alocal, final OtpErlangPid aremote) { return local.equals(alocal) && remote.equals(aremote) || local.equals(aremote) && remote.equals(alocal); } @Override public int hashCode() { if (hashCodeValue == 0) { final OtpErlangObject.Hash hash = new OtpErlangObject.Hash(5); hash.combine(local.hashCode() + remote.hashCode()); hashCodeValue = hash.valueOf(); } return hashCodeValue; } }