Java client BECN implementation (#155)
This commit is contained in:
committed by
Jason Watkins
parent
90ccec0d07
commit
c018d6c3be
2
Java/.idea/misc.xml
generated
2
Java/.idea/misc.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
1
Java/.idea/modules.xml
generated
1
Java/.idea/modules.xml
generated
@@ -2,6 +2,7 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/Examples/DiscoveryExample/DiscoveryExample.iml" filepath="$PROJECT_DIR$/Examples/DiscoveryExample/DiscoveryExample.iml" />
|
||||||
<module fileurl="file://$PROJECT_DIR$/XPlaneConnect.iml" filepath="$PROJECT_DIR$/XPlaneConnect.iml" />
|
<module fileurl="file://$PROJECT_DIR$/XPlaneConnect.iml" filepath="$PROJECT_DIR$/XPlaneConnect.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package gov.nasa.xpc.ex;
|
package gov.nasa.xpc.ex;
|
||||||
|
|
||||||
import gov.nasa.xpc.XPlaneConnect;
|
import gov.nasa.xpc.XPlaneConnect;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|||||||
12
Java/Examples/DiscoveryExample/DiscoveryExample.iml
Normal file
12
Java/Examples/DiscoveryExample/DiscoveryExample.iml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="gov.nasa.xpc.ex" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="module" module-name="XPlaneConnect" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package gov.nasa.xpc.ex;
|
||||||
|
|
||||||
|
import gov.nasa.xpc.XPlaneConnect;
|
||||||
|
import gov.nasa.xpc.discovery.XPlaneConnectDiscovery;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.SocketException;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class DiscoveryConnectionExample {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
|
||||||
|
XPlaneConnectDiscovery discovery = new XPlaneConnectDiscovery();
|
||||||
|
discovery.start(xpc -> {
|
||||||
|
|
||||||
|
sendDref(xpc);
|
||||||
|
});
|
||||||
|
System.out.println("Example done");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sendDref(XPlaneConnect xpc) {
|
||||||
|
System.out.println("Sending DREF");
|
||||||
|
try {
|
||||||
|
xpc.getDREF("sim/test/test_float");
|
||||||
|
|
||||||
|
} catch (SocketException e) {
|
||||||
|
System.out.println("Unable to set up the connection. (Error message was '" + e.getMessage() + "'.)");
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("Something went wrong with one of the commands. (Error message was '" + e.getMessage() + "'.)");
|
||||||
|
}
|
||||||
|
System.out.println("Sending DREF done");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package gov.nasa.xpc.ex;
|
||||||
|
|
||||||
|
import gov.nasa.xpc.discovery.XPlaneConnectDiscovery;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XPlaneConnect discovery example that prints continuously each BECN packet
|
||||||
|
*/
|
||||||
|
public class SimpleDiscoveryExample {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
XPlaneConnectDiscovery discovery = new XPlaneConnectDiscovery();
|
||||||
|
discovery.onBeaconReceived(beacon -> {
|
||||||
|
System.out.println("Discovered XPlaneConnect plugin:");
|
||||||
|
System.out.println("Plugin version: " + beacon.getPluginVersion());
|
||||||
|
System.out.println("X-Plane version: " + beacon.getXplaneVersion());
|
||||||
|
System.out.println("Address: " + beacon.getXplaneAddress().getHostAddress() + ":" + beacon.getPluginPort());
|
||||||
|
});
|
||||||
|
discovery.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="JAVA_MODULE" version="4">
|
<module type="JAVA_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
|
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="true">
|
||||||
<exclude-output />
|
<exclude-output />
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="gov.nasa.xpc" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="gov.nasa.xpc" />
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<content url="file://$MODULE_DIR$/../TestScripts/Java Tests">
|
<content url="file://$MODULE_DIR$/../TestScripts/Java Tests">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/../TestScripts/Java Tests" isTestSource="true" packagePrefix="gov.nasa.xpc.test" />
|
<sourceFolder url="file://$MODULE_DIR$/../TestScripts/Java Tests" isTestSource="true" packagePrefix="gov.nasa.xpc.test" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="module-library" scope="TEST">
|
<orderEntry type="module-library" scope="TEST">
|
||||||
<library name="JUnit4">
|
<library name="JUnit4">
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
package gov.nasa.xpc;
|
package gov.nasa.xpc;
|
||||||
|
|
||||||
|
import gov.nasa.xpc.discovery.Beacon;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.AutoCloseable;
|
import java.lang.AutoCloseable;
|
||||||
@@ -136,6 +138,19 @@ public class XPlaneConnect implements AutoCloseable
|
|||||||
this(xpHost, xpPort, port, 100);
|
this(xpHost, xpPort, port, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new instance of the {@code XPlaneConnect} class from a received discovery Beacon
|
||||||
|
* @param beacon The beacon received from {@code XPlaneConnectDiscovery}
|
||||||
|
* @throws SocketException If this instance is unable to bind to the specified port.
|
||||||
|
*/
|
||||||
|
public XPlaneConnect(Beacon beacon) throws SocketException {
|
||||||
|
this.socket = new DatagramSocket(0);
|
||||||
|
this.xplaneAddr = beacon.getXplaneAddress();
|
||||||
|
this.xplanePort = beacon.getPluginPort();
|
||||||
|
this.socket.setSoTimeout(100);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a new instance of the {@code XPlaneConnect} class using the specified ports, hostname, and timeout.
|
* Initializes a new instance of the {@code XPlaneConnect} class using the specified ports, hostname, and timeout.
|
||||||
*
|
*
|
||||||
|
|||||||
45
Java/src/discovery/Beacon.java
Normal file
45
Java/src/discovery/Beacon.java
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package gov.nasa.xpc.discovery;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
|
||||||
|
|
||||||
|
public class Beacon {
|
||||||
|
|
||||||
|
private InetAddress xplaneAddress;
|
||||||
|
private int pluginPort;
|
||||||
|
private String pluginVersion;
|
||||||
|
private int xPlaneVersion;
|
||||||
|
|
||||||
|
|
||||||
|
public Beacon(InetAddress xplaneAddress, int pluginPort, String pluginVersion, int xPlaneVersion) {
|
||||||
|
this.xplaneAddress = xplaneAddress;
|
||||||
|
this.pluginPort = pluginPort;
|
||||||
|
this.pluginVersion = pluginVersion;
|
||||||
|
this.xPlaneVersion = xPlaneVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHost() {
|
||||||
|
return xplaneAddress.getHostAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPluginVersion() {
|
||||||
|
return pluginVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getXplaneVersion() {
|
||||||
|
return String.format("%.2f", xPlaneVersion / 100.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPluginPort() {
|
||||||
|
return pluginPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InetAddress getXplaneAddress() {
|
||||||
|
return xplaneAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "host: " + getHost() + ":" + getPluginPort() +" version: " + getPluginVersion() + " X-Plane Version: " + getXplaneVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
54
Java/src/discovery/BeaconParser.java
Normal file
54
Java/src/discovery/BeaconParser.java
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package gov.nasa.xpc.discovery;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parser class that parses the UDP discovery BECN packet. The format of the packet is
|
||||||
|
* - 4 bytes: BECN
|
||||||
|
* - 1 byte: 0
|
||||||
|
* - 2 bytes: XPlaneConnect server port e.g. '49009'
|
||||||
|
* - 4 bytes: X-Plane version e.g. '11260'
|
||||||
|
* - null terminated string: XPlaneConnect plugin version e.g. '1.3-rc.1'
|
||||||
|
*/
|
||||||
|
public class BeaconParser {
|
||||||
|
|
||||||
|
private static String BECN = "BECN";
|
||||||
|
|
||||||
|
private static int XPC_PORT_OFFSET = BECN.length() + 1;
|
||||||
|
private static int XPC_PORT_LEN = 2;
|
||||||
|
private static int XPC_VERSION_OFFSET = XPC_PORT_OFFSET + XPC_PORT_LEN;
|
||||||
|
private static int XPC_VERSION_LEN = 4;
|
||||||
|
private static int XPC_PLUGIN_VERSION_OFFSET = XPC_VERSION_OFFSET + XPC_VERSION_LEN;
|
||||||
|
|
||||||
|
|
||||||
|
public Beacon readBCN(DatagramPacket packet) throws IOException {
|
||||||
|
if (packet.getLength() < BECN.length()) {
|
||||||
|
throw new IOException("BECN response too short");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] data = packet.getData();
|
||||||
|
|
||||||
|
String command = new String(data, 0, BECN.length());
|
||||||
|
if (!command.equals(BECN)) {
|
||||||
|
throw new IOException("Expected " + BECN + " got '" + command + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
InetAddress address = packet.getAddress();
|
||||||
|
ByteBuffer bb = ByteBuffer.wrap(data);
|
||||||
|
bb.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
// 2 bytes: port as short + converted to an int
|
||||||
|
int port = bb.getShort(XPC_PORT_OFFSET) & 0xffff;
|
||||||
|
|
||||||
|
// 4 bytes: x plane version as int
|
||||||
|
int version = bb.getInt(XPC_VERSION_OFFSET);
|
||||||
|
|
||||||
|
// plugin version
|
||||||
|
String pluginVersion = new String(data, XPC_PLUGIN_VERSION_OFFSET, packet.getLength() - XPC_PLUGIN_VERSION_OFFSET);
|
||||||
|
|
||||||
|
return new Beacon(address, port, pluginVersion.trim(), version);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Java/src/discovery/BeaconReceivedListener.java
Normal file
10
Java/src/discovery/BeaconReceivedListener.java
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package gov.nasa.xpc.discovery;
|
||||||
|
|
||||||
|
public interface BeaconReceivedListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called every time a BECN packet is received
|
||||||
|
* @param beacon The parsed beacon
|
||||||
|
*/
|
||||||
|
void onBeaconReceived(Beacon beacon);
|
||||||
|
}
|
||||||
14
Java/src/discovery/DiscoveryConnectionCallback.java
Normal file
14
Java/src/discovery/DiscoveryConnectionCallback.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package gov.nasa.xpc.discovery;
|
||||||
|
|
||||||
|
import gov.nasa.xpc.XPlaneConnect;
|
||||||
|
|
||||||
|
public interface DiscoveryConnectionCallback {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper callback called when a 1st XPlanePlugin is discovered. When the 1st packet is received, an XPlaneConnect
|
||||||
|
* instance is created and the discovery is stopped.
|
||||||
|
*
|
||||||
|
* @param xpc The XPlaneConnect instance configured with the discovered
|
||||||
|
*/
|
||||||
|
void onConnectionEstablished(XPlaneConnect xpc);
|
||||||
|
}
|
||||||
94
Java/src/discovery/XPlaneConnectDiscovery.java
Normal file
94
Java/src/discovery/XPlaneConnectDiscovery.java
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package gov.nasa.xpc.discovery;
|
||||||
|
|
||||||
|
import gov.nasa.xpc.XPlaneConnect;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.MulticastSocket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The XPlaneConnectDiscovery joins a UDP multi cast group and listens for BECN packets published by
|
||||||
|
* XPlaneConnect server plugin. It allows to clients to discover XPlane instances and be sure that the XPlanePlugin is
|
||||||
|
* installed.
|
||||||
|
*/
|
||||||
|
public class XPlaneConnectDiscovery implements AutoCloseable {
|
||||||
|
|
||||||
|
private static int DEFAULT_PORT = 49710;
|
||||||
|
private static String DEFAULT_ADDRESS = "239.255.1.1";
|
||||||
|
|
||||||
|
private BeaconReceivedListener mListener;
|
||||||
|
|
||||||
|
private MulticastSocket socket = null;
|
||||||
|
private byte[] buf = new byte[256];
|
||||||
|
|
||||||
|
private int mPort;
|
||||||
|
private String mAddress;
|
||||||
|
private BeaconParser parser = new BeaconParser();
|
||||||
|
|
||||||
|
private XPlaneConnectDiscovery(int port, String address) {
|
||||||
|
mPort = port;
|
||||||
|
mAddress = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XPlaneConnectDiscovery() {
|
||||||
|
this(DEFAULT_PORT, DEFAULT_ADDRESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start(DiscoveryConnectionCallback callback) throws IOException {
|
||||||
|
onBeaconReceived(beacon -> {
|
||||||
|
this.close();
|
||||||
|
|
||||||
|
try {
|
||||||
|
XPlaneConnect xpc = new XPlaneConnect(beacon);
|
||||||
|
callback.onConnectionEstablished(xpc);
|
||||||
|
} catch (SocketException e) {
|
||||||
|
System.err.println("Could not connect to a discovered XplaneConnect " +beacon+ ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() throws IOException {
|
||||||
|
System.out.println("Starting XPlane Connect discovery");
|
||||||
|
socket = new MulticastSocket(mPort);
|
||||||
|
InetAddress group = InetAddress.getByName(mAddress);
|
||||||
|
socket.joinGroup(group);
|
||||||
|
while (socket != null) {
|
||||||
|
DatagramPacket packet = new DatagramPacket(buf, buf.length);
|
||||||
|
socket.receive(packet);
|
||||||
|
if (mListener == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Beacon beacon = parser.readBCN(packet);
|
||||||
|
mListener.onBeaconReceived(beacon);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
System.err.println("Received packet on discovery group but could not parse it: " + ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close();
|
||||||
|
System.out.println("XPlane Connect discovery ended");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void onBeaconReceived(BeaconReceivedListener mListener) {
|
||||||
|
this.mListener = mListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (socket != null) {
|
||||||
|
socket.close();
|
||||||
|
socket = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
73
TestScripts/Java Tests/XPlaneDiscoveryTests.java
Normal file
73
TestScripts/Java Tests/XPlaneDiscoveryTests.java
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package gov.nasa.xpc.test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import gov.nasa.xpc.discovery.Beacon;
|
||||||
|
import gov.nasa.xpc.discovery.BeaconParser;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
|
||||||
|
|
||||||
|
public class XPlaneDiscoveryTests {
|
||||||
|
|
||||||
|
private BeaconParser parser;
|
||||||
|
private DatagramPacket packet;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
parser = new BeaconParser();
|
||||||
|
byte[] bytes = HexHelper.decodeHexString("4245434E0071BFFC2B0000312E332D72632E310000000000000000");
|
||||||
|
packet = new DatagramPacket(bytes, 0, bytes.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readBeaconParsesPort() throws IOException {
|
||||||
|
Beacon beacon = parser.readBCN(packet);
|
||||||
|
assertEquals(beacon.getPluginPort(), 49009);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readBeaconParsesXplaneVersion() throws IOException {
|
||||||
|
Beacon beacon = parser.readBCN(packet);
|
||||||
|
assertEquals(beacon.getXplaneVersion(), "112.60");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readBeaconParsesPluginVersion() throws IOException {
|
||||||
|
Beacon beacon = parser.readBCN(packet);
|
||||||
|
assertEquals(beacon.getPluginVersion(), "1.3-rc.1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HexHelper {
|
||||||
|
static byte[] decodeHexString(String hexString) {
|
||||||
|
if (hexString.length() % 2 == 1) {
|
||||||
|
throw new IllegalArgumentException("Invalid hexadecimal String supplied.");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = new byte[hexString.length() / 2];
|
||||||
|
for (int i = 0; i < hexString.length(); i += 2) {
|
||||||
|
bytes[i / 2] = hexToByte(hexString.substring(i, i + 2));
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte hexToByte(String hexString) {
|
||||||
|
int firstDigit = toDigit(hexString.charAt(0));
|
||||||
|
int secondDigit = toDigit(hexString.charAt(1));
|
||||||
|
return (byte) ((firstDigit << 4) + secondDigit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int toDigit(char hexChar) {
|
||||||
|
int digit = Character.digit(hexChar, 16);
|
||||||
|
if (digit == -1) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Invalid Hexadecimal Character: " + hexChar);
|
||||||
|
}
|
||||||
|
return digit;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user