mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-26 01:51:41 +00:00
[wpiutil] Rename Java package to edu.wpi.first.util (#3431)
This is more consistent with wpimath being edu.wpi.first.math.
This commit is contained in:
191
wpiutil/src/main/java/edu/wpi/first/util/CircularBuffer.java
Normal file
191
wpiutil/src/main/java/edu/wpi/first/util/CircularBuffer.java
Normal file
@@ -0,0 +1,191 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.util;
|
||||
|
||||
/** This is a simple circular buffer so we don't need to "bucket brigade" copy old values. */
|
||||
public class CircularBuffer {
|
||||
private double[] m_data;
|
||||
|
||||
// Index of element at front of buffer
|
||||
private int m_front;
|
||||
|
||||
// Number of elements used in buffer
|
||||
private int m_length;
|
||||
|
||||
/**
|
||||
* Create a CircularBuffer with the provided size.
|
||||
*
|
||||
* @param size The size of the circular buffer.
|
||||
*/
|
||||
public CircularBuffer(int size) {
|
||||
m_data = new double[size];
|
||||
for (int i = 0; i < m_data.length; i++) {
|
||||
m_data[i] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of elements in buffer.
|
||||
*
|
||||
* @return number of elements in buffer
|
||||
*/
|
||||
double size() {
|
||||
return m_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value at front of buffer.
|
||||
*
|
||||
* @return value at front of buffer
|
||||
*/
|
||||
double getFirst() {
|
||||
return m_data[m_front];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value at back of buffer.
|
||||
*
|
||||
* @return value at back of buffer
|
||||
*/
|
||||
double getLast() {
|
||||
// If there are no elements in the buffer, do nothing
|
||||
if (m_length == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return m_data[(m_front + m_length - 1) % m_data.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Push new value onto front of the buffer. The value at the back is overwritten if the buffer is
|
||||
* full.
|
||||
*
|
||||
* @param value The value to push.
|
||||
*/
|
||||
public void addFirst(double value) {
|
||||
if (m_data.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_front = moduloDec(m_front);
|
||||
|
||||
m_data[m_front] = value;
|
||||
|
||||
if (m_length < m_data.length) {
|
||||
m_length++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push new value onto back of the buffer. The value at the front is overwritten if the buffer is
|
||||
* full.
|
||||
*
|
||||
* @param value The value to push.
|
||||
*/
|
||||
public void addLast(double value) {
|
||||
if (m_data.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_data[(m_front + m_length) % m_data.length] = value;
|
||||
|
||||
if (m_length < m_data.length) {
|
||||
m_length++;
|
||||
} else {
|
||||
// Increment front if buffer is full to maintain size
|
||||
m_front = moduloInc(m_front);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop value at front of buffer.
|
||||
*
|
||||
* @return value at front of buffer
|
||||
*/
|
||||
public double removeFirst() {
|
||||
// If there are no elements in the buffer, do nothing
|
||||
if (m_length == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double temp = m_data[m_front];
|
||||
m_front = moduloInc(m_front);
|
||||
m_length--;
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop value at back of buffer.
|
||||
*
|
||||
* @return value at back of buffer
|
||||
*/
|
||||
public double removeLast() {
|
||||
// If there are no elements in the buffer, do nothing
|
||||
if (m_length == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
m_length--;
|
||||
return m_data[(m_front + m_length) % m_data.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes internal buffer to given size.
|
||||
*
|
||||
* <p>A new buffer is allocated because arrays are not resizable.
|
||||
*
|
||||
* @param size New buffer size.
|
||||
*/
|
||||
void resize(int size) {
|
||||
double[] newBuffer = new double[size];
|
||||
m_length = Math.min(m_length, size);
|
||||
for (int i = 0; i < m_length; i++) {
|
||||
newBuffer[i] = m_data[(m_front + i) % m_data.length];
|
||||
}
|
||||
m_data = newBuffer;
|
||||
m_front = 0;
|
||||
}
|
||||
|
||||
/** Sets internal buffer contents to zero. */
|
||||
public void clear() {
|
||||
for (int i = 0; i < m_data.length; i++) {
|
||||
m_data[i] = 0.0;
|
||||
}
|
||||
m_front = 0;
|
||||
m_length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element at the provided index relative to the start of the buffer.
|
||||
*
|
||||
* @param index Index into the buffer.
|
||||
* @return Element at index starting from front of buffer.
|
||||
*/
|
||||
public double get(int index) {
|
||||
return m_data[(m_front + index) % m_data.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment an index modulo the length of the m_data buffer.
|
||||
*
|
||||
* @param index Index into the buffer.
|
||||
*/
|
||||
private int moduloInc(int index) {
|
||||
return (index + 1) % m_data.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement an index modulo the length of the m_data buffer.
|
||||
*
|
||||
* @param index Index into the buffer.
|
||||
*/
|
||||
private int moduloDec(int index) {
|
||||
if (index == 0) {
|
||||
return m_data.length - 1;
|
||||
} else {
|
||||
return index - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.util;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class CombinedRuntimeLoader {
|
||||
private CombinedRuntimeLoader() {}
|
||||
|
||||
private static String extractionDirectory;
|
||||
|
||||
public static synchronized String getExtractionDirectory() {
|
||||
return extractionDirectory;
|
||||
}
|
||||
|
||||
private static synchronized void setExtractionDirectory(String directory) {
|
||||
extractionDirectory = directory;
|
||||
}
|
||||
|
||||
public static native String setDllDirectory(String directory);
|
||||
|
||||
private static String getLoadErrorMessage(String libraryName, UnsatisfiedLinkError ule) {
|
||||
StringBuilder msg = new StringBuilder(512);
|
||||
msg.append(libraryName)
|
||||
.append(" could not be loaded from path\n" + "\tattempted to load for platform ")
|
||||
.append(RuntimeDetector.getPlatformPath())
|
||||
.append("\nLast Load Error: \n")
|
||||
.append(ule.getMessage())
|
||||
.append('\n');
|
||||
if (RuntimeDetector.isWindows()) {
|
||||
msg.append(
|
||||
"A common cause of this error is missing the C++ runtime.\n"
|
||||
+ "Download the latest at https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads\n");
|
||||
}
|
||||
return msg.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a list of native libraries.
|
||||
*
|
||||
* @param <T> The class where the resources would be located
|
||||
* @param clazz The actual class object
|
||||
* @param resourceName The resource name on the classpath to use for file lookup
|
||||
* @return List of all libraries that were extracted
|
||||
* @throws IOException Thrown if resource not found or file could not be extracted
|
||||
*/
|
||||
@SuppressWarnings({"PMD.UnnecessaryCastRule", "unchecked"})
|
||||
public static <T> List<String> extractLibraries(Class<T> clazz, String resourceName)
|
||||
throws IOException {
|
||||
TypeReference<HashMap<String, Object>> typeRef =
|
||||
new TypeReference<HashMap<String, Object>>() {};
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Map<String, Object> map;
|
||||
try (var stream = clazz.getResourceAsStream(resourceName)) {
|
||||
map = mapper.readValue(stream, typeRef);
|
||||
}
|
||||
|
||||
var platformPath = Paths.get(RuntimeDetector.getPlatformPath());
|
||||
var platform = platformPath.getName(0).toString();
|
||||
var arch = platformPath.getName(1).toString();
|
||||
|
||||
var platformMap = (Map<String, List<String>>) map.get(platform);
|
||||
|
||||
var fileList = platformMap.get(arch);
|
||||
|
||||
var extractionPathString = getExtractionDirectory();
|
||||
|
||||
if (extractionPathString == null) {
|
||||
String hash = (String) map.get("hash");
|
||||
|
||||
var defaultExtractionRoot = RuntimeLoader.getDefaultExtractionRoot();
|
||||
var extractionPath = Paths.get(defaultExtractionRoot, platform, arch, hash);
|
||||
extractionPathString = extractionPath.toString();
|
||||
|
||||
setExtractionDirectory(extractionPathString);
|
||||
}
|
||||
|
||||
List<String> extractedFiles = new ArrayList<>();
|
||||
|
||||
byte[] buffer = new byte[0x10000]; // 64K copy buffer
|
||||
|
||||
for (var file : fileList) {
|
||||
try (var stream = clazz.getResourceAsStream(file)) {
|
||||
Objects.requireNonNull(stream);
|
||||
|
||||
var outputFile = Paths.get(extractionPathString, new File(file).getName());
|
||||
extractedFiles.add(outputFile.toString());
|
||||
if (outputFile.toFile().exists()) {
|
||||
continue;
|
||||
}
|
||||
outputFile.getParent().toFile().mkdirs();
|
||||
|
||||
try (var os = Files.newOutputStream(outputFile)) {
|
||||
int readBytes;
|
||||
while ((readBytes = stream.read(buffer)) != -1) { // NOPMD
|
||||
os.write(buffer, 0, readBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extractedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a single library from a list of extracted files.
|
||||
*
|
||||
* @param libraryName The library name to load
|
||||
* @param extractedFiles The extracted files to search
|
||||
* @throws IOException If library was not found
|
||||
*/
|
||||
public static void loadLibrary(String libraryName, List<String> extractedFiles)
|
||||
throws IOException {
|
||||
String currentPath = null;
|
||||
String oldDllDirectory = null;
|
||||
try {
|
||||
if (RuntimeDetector.isWindows()) {
|
||||
var extractionPathString = getExtractionDirectory();
|
||||
oldDllDirectory = setDllDirectory(extractionPathString);
|
||||
}
|
||||
for (var extractedFile : extractedFiles) {
|
||||
if (extractedFile.contains(libraryName)) {
|
||||
// Load it
|
||||
currentPath = extractedFile;
|
||||
System.load(extractedFile);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IOException("Could not find library " + libraryName);
|
||||
} catch (UnsatisfiedLinkError ule) {
|
||||
throw new IOException(getLoadErrorMessage(currentPath, ule));
|
||||
} finally {
|
||||
if (oldDllDirectory != null) {
|
||||
setDllDirectory(oldDllDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a list of native libraries out of a single directory.
|
||||
*
|
||||
* @param <T> The class where the resources would be located
|
||||
* @param clazz The actual class object
|
||||
* @param librariesToLoad List of libraries to load
|
||||
* @throws IOException Throws an IOException if not found
|
||||
*/
|
||||
public static <T> void loadLibraries(Class<T> clazz, String... librariesToLoad)
|
||||
throws IOException {
|
||||
// Extract everything
|
||||
|
||||
var extractedFiles = extractLibraries(clazz, "/ResourceInformation.json");
|
||||
|
||||
String currentPath = "";
|
||||
|
||||
try {
|
||||
if (RuntimeDetector.isWindows()) {
|
||||
var extractionPathString = getExtractionDirectory();
|
||||
// Load windows, set dll directory
|
||||
currentPath = Paths.get(extractionPathString, "WindowsLoaderHelper.dll").toString();
|
||||
System.load(currentPath);
|
||||
}
|
||||
} catch (UnsatisfiedLinkError ule) {
|
||||
throw new IOException(getLoadErrorMessage(currentPath, ule));
|
||||
}
|
||||
|
||||
for (var library : librariesToLoad) {
|
||||
loadLibrary(library, extractedFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
41
wpiutil/src/main/java/edu/wpi/first/util/ErrorMessages.java
Normal file
41
wpiutil/src/main/java/edu/wpi/first/util/ErrorMessages.java
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.util;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/** Utility class for common WPILib error messages. */
|
||||
public final class ErrorMessages {
|
||||
/** Utility class, so constructor is private. */
|
||||
private ErrorMessages() {
|
||||
throw new UnsupportedOperationException("This is a utility class!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Requires that a parameter of a method not be null; prints an error message with helpful
|
||||
* debugging instructions if the parameter is null.
|
||||
*
|
||||
* @param <T> Type of object.
|
||||
* @param obj The parameter that must not be null.
|
||||
* @param paramName The name of the parameter.
|
||||
* @param methodName The name of the method.
|
||||
* @return The object parameter confirmed not to be null.
|
||||
*/
|
||||
public static <T> T requireNonNullParam(T obj, String paramName, String methodName) {
|
||||
return requireNonNull(
|
||||
obj,
|
||||
"Parameter "
|
||||
+ paramName
|
||||
+ " in method "
|
||||
+ methodName
|
||||
+ " was null when it"
|
||||
+ " should not have been! Check the stacktrace to find the responsible line of code - "
|
||||
+ "usually, it is the first line of user-written code indicated in the stacktrace. "
|
||||
+ "Make sure all objects passed to the method in question were properly initialized -"
|
||||
+ " note that this may not be obvious if it is being called under "
|
||||
+ "dynamically-changing conditions! Please do not seek additional technical assistance"
|
||||
+ " without doing this first!");
|
||||
}
|
||||
}
|
||||
176
wpiutil/src/main/java/edu/wpi/first/util/RuntimeDetector.java
Normal file
176
wpiutil/src/main/java/edu/wpi/first/util/RuntimeDetector.java
Normal file
@@ -0,0 +1,176 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public final class RuntimeDetector {
|
||||
private static String filePrefix;
|
||||
private static String fileExtension;
|
||||
private static String filePath;
|
||||
|
||||
private static synchronized void computePlatform() {
|
||||
if (fileExtension != null && filePath != null && filePrefix != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean intel32 = is32BitIntel();
|
||||
boolean intel64 = is64BitIntel();
|
||||
|
||||
if (isWindows()) {
|
||||
filePrefix = "";
|
||||
fileExtension = ".dll";
|
||||
if (intel32) {
|
||||
filePath = "/windows/x86/";
|
||||
} else {
|
||||
filePath = "/windows/x86-64/";
|
||||
}
|
||||
} else if (isMac()) {
|
||||
filePrefix = "lib";
|
||||
fileExtension = ".dylib";
|
||||
if (intel32) {
|
||||
filePath = "/osx/x86";
|
||||
} else {
|
||||
filePath = "/osx/x86-64/";
|
||||
}
|
||||
} else if (isLinux()) {
|
||||
filePrefix = "lib";
|
||||
fileExtension = ".so";
|
||||
if (intel32) {
|
||||
filePath = "/linux/x86/";
|
||||
} else if (intel64) {
|
||||
filePath = "/linux/x86-64/";
|
||||
} else if (isAthena()) {
|
||||
filePath = "/linux/athena/";
|
||||
} else if (isRaspbian()) {
|
||||
filePath = "/linux/raspbian/";
|
||||
} else if (isAarch64()) {
|
||||
filePath = "/linux/aarch64bionic/";
|
||||
} else {
|
||||
filePath = "/linux/nativearm/";
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Failed to determine OS");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file prefix for the current system.
|
||||
*
|
||||
* @return The file prefix.
|
||||
*/
|
||||
public static synchronized String getFilePrefix() {
|
||||
computePlatform();
|
||||
|
||||
return filePrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file extension for the current system.
|
||||
*
|
||||
* @return The file extension.
|
||||
*/
|
||||
public static synchronized String getFileExtension() {
|
||||
computePlatform();
|
||||
|
||||
return fileExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the platform path for the current system.
|
||||
*
|
||||
* @return The platform path.
|
||||
*/
|
||||
public static synchronized String getPlatformPath() {
|
||||
computePlatform();
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the requested resource.
|
||||
*
|
||||
* @param libName Library name.
|
||||
* @return The path to the requested resource.
|
||||
*/
|
||||
public static synchronized String getLibraryResource(String libName) {
|
||||
computePlatform();
|
||||
|
||||
return filePath + filePrefix + libName + fileExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the hash to the requested resource.
|
||||
*
|
||||
* @param libName Library name.
|
||||
* @return The path to the hash to the requested resource.
|
||||
*/
|
||||
public static synchronized String getHashLibraryResource(String libName) {
|
||||
computePlatform();
|
||||
|
||||
return filePath + libName + ".hash";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if hardware platform is Athena.
|
||||
*
|
||||
* @return True if hardware platform is Athena.
|
||||
*/
|
||||
public static boolean isAthena() {
|
||||
File runRobotFile = new File("/usr/local/frc/bin/frcRunRobot.sh");
|
||||
return runRobotFile.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if OS is Raspbian.
|
||||
*
|
||||
* @return True if OS is Raspbian.
|
||||
*/
|
||||
public static boolean isRaspbian() {
|
||||
try (BufferedReader reader = Files.newBufferedReader(Paths.get("/etc/os-release"))) {
|
||||
String value = reader.readLine();
|
||||
return value.contains("Raspbian");
|
||||
} catch (IOException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if architecture is aarch64.
|
||||
*
|
||||
* @return if architecture is aarch64
|
||||
*/
|
||||
public static boolean isAarch64() {
|
||||
return System.getProperty("os.arch").equals("aarch64");
|
||||
}
|
||||
|
||||
public static boolean isLinux() {
|
||||
return System.getProperty("os.name").startsWith("Linux");
|
||||
}
|
||||
|
||||
public static boolean isWindows() {
|
||||
return System.getProperty("os.name").startsWith("Windows");
|
||||
}
|
||||
|
||||
public static boolean isMac() {
|
||||
return System.getProperty("os.name").startsWith("Mac");
|
||||
}
|
||||
|
||||
public static boolean is32BitIntel() {
|
||||
String arch = System.getProperty("os.arch");
|
||||
return "x86".equals(arch) || "i386".equals(arch);
|
||||
}
|
||||
|
||||
public static boolean is64BitIntel() {
|
||||
String arch = System.getProperty("os.arch");
|
||||
return "amd64".equals(arch) || "x86_64".equals(arch);
|
||||
}
|
||||
|
||||
private RuntimeDetector() {}
|
||||
}
|
||||
187
wpiutil/src/main/java/edu/wpi/first/util/RuntimeLoader.java
Normal file
187
wpiutil/src/main/java/edu/wpi/first/util/RuntimeLoader.java
Normal file
@@ -0,0 +1,187 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.DigestInputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Locale;
|
||||
import java.util.Scanner;
|
||||
|
||||
public final class RuntimeLoader<T> {
|
||||
private static String defaultExtractionRoot;
|
||||
|
||||
/**
|
||||
* Gets the default extration root location (~/.wpilib/nativecache).
|
||||
*
|
||||
* @return The default extraction root location.
|
||||
*/
|
||||
public static synchronized String getDefaultExtractionRoot() {
|
||||
if (defaultExtractionRoot != null) {
|
||||
return defaultExtractionRoot;
|
||||
}
|
||||
String home = System.getProperty("user.home");
|
||||
defaultExtractionRoot = Paths.get(home, ".wpilib", "nativecache").toString();
|
||||
return defaultExtractionRoot;
|
||||
}
|
||||
|
||||
private final String m_libraryName;
|
||||
private final Class<T> m_loadClass;
|
||||
private final String m_extractionRoot;
|
||||
|
||||
/**
|
||||
* Creates a new library loader.
|
||||
*
|
||||
* @param libraryName Name of library to load.
|
||||
* @param extractionRoot Location from which to load the library.
|
||||
* @param cls Class whose classpath the given library belongs.
|
||||
*/
|
||||
public RuntimeLoader(String libraryName, String extractionRoot, Class<T> cls) {
|
||||
m_libraryName = libraryName;
|
||||
m_loadClass = cls;
|
||||
m_extractionRoot = extractionRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a load error message given the information in the provided UnsatisfiedLinkError.
|
||||
*
|
||||
* @param ule UnsatisfiedLinkError object.
|
||||
* @return A load error message.
|
||||
*/
|
||||
private String getLoadErrorMessage(UnsatisfiedLinkError ule) {
|
||||
StringBuilder msg = new StringBuilder(512);
|
||||
msg.append(m_libraryName)
|
||||
.append(
|
||||
" could not be loaded from path or an embedded resource.\n"
|
||||
+ "\tattempted to load for platform ")
|
||||
.append(RuntimeDetector.getPlatformPath())
|
||||
.append("\nLast Load Error: \n")
|
||||
.append(ule.getMessage())
|
||||
.append('\n');
|
||||
if (RuntimeDetector.isWindows()) {
|
||||
msg.append(
|
||||
"A common cause of this error is missing the C++ runtime.\n"
|
||||
+ "Download the latest at https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads\n");
|
||||
}
|
||||
return msg.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a native library.
|
||||
*
|
||||
* @throws IOException if the library fails to load
|
||||
*/
|
||||
@SuppressWarnings("PMD.PreserveStackTrace")
|
||||
public void loadLibrary() throws IOException {
|
||||
try {
|
||||
// First, try loading path
|
||||
System.loadLibrary(m_libraryName);
|
||||
} catch (UnsatisfiedLinkError ule) {
|
||||
// Then load the hash from the resources
|
||||
String hashName = RuntimeDetector.getHashLibraryResource(m_libraryName);
|
||||
String resname = RuntimeDetector.getLibraryResource(m_libraryName);
|
||||
try (InputStream hashIs = m_loadClass.getResourceAsStream(hashName)) {
|
||||
if (hashIs == null) {
|
||||
throw new IOException(getLoadErrorMessage(ule));
|
||||
}
|
||||
try (Scanner scanner = new Scanner(hashIs, StandardCharsets.UTF_8.name())) {
|
||||
String hash = scanner.nextLine();
|
||||
File jniLibrary = new File(m_extractionRoot, resname + "." + hash);
|
||||
try {
|
||||
// Try to load from an already extracted hash
|
||||
System.load(jniLibrary.getAbsolutePath());
|
||||
} catch (UnsatisfiedLinkError ule2) {
|
||||
// If extraction failed, extract
|
||||
try (InputStream resIs = m_loadClass.getResourceAsStream(resname)) {
|
||||
if (resIs == null) {
|
||||
throw new IOException(getLoadErrorMessage(ule));
|
||||
}
|
||||
jniLibrary.getParentFile().mkdirs();
|
||||
try (OutputStream os = Files.newOutputStream(jniLibrary.toPath())) {
|
||||
byte[] buffer = new byte[0xFFFF]; // 64K copy buffer
|
||||
int readBytes;
|
||||
while ((readBytes = resIs.read(buffer)) != -1) { // NOPMD
|
||||
os.write(buffer, 0, readBytes);
|
||||
}
|
||||
}
|
||||
System.load(jniLibrary.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a native library by directly hashing the file.
|
||||
*
|
||||
* @throws IOException if the library failed to load
|
||||
*/
|
||||
@SuppressWarnings({"PMD.PreserveStackTrace", "PMD.EmptyWhileStmt"})
|
||||
public void loadLibraryHashed() throws IOException {
|
||||
try {
|
||||
// First, try loading path
|
||||
System.loadLibrary(m_libraryName);
|
||||
} catch (UnsatisfiedLinkError ule) {
|
||||
// Then load the hash from the input file
|
||||
String resname = RuntimeDetector.getLibraryResource(m_libraryName);
|
||||
String hash = null;
|
||||
try (InputStream is = m_loadClass.getResourceAsStream(resname)) {
|
||||
if (is == null) {
|
||||
throw new IOException(getLoadErrorMessage(ule));
|
||||
}
|
||||
MessageDigest md = null;
|
||||
try {
|
||||
md = MessageDigest.getInstance("MD5");
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
throw new RuntimeException("Weird Hash Algorithm?");
|
||||
}
|
||||
try (DigestInputStream dis = new DigestInputStream(is, md)) {
|
||||
// Read the entire buffer once to hash
|
||||
byte[] buffer = new byte[0xFFFF];
|
||||
while (dis.read(buffer) > -1) {}
|
||||
MessageDigest digest = dis.getMessageDigest();
|
||||
byte[] digestOutput = digest.digest();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (byte b : digestOutput) {
|
||||
builder.append(String.format("%02X", b));
|
||||
}
|
||||
hash = builder.toString().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
if (hash == null) {
|
||||
throw new IOException("Weird Hash?");
|
||||
}
|
||||
File jniLibrary = new File(m_extractionRoot, resname + "." + hash);
|
||||
try {
|
||||
// Try to load from an already extracted hash
|
||||
System.load(jniLibrary.getAbsolutePath());
|
||||
} catch (UnsatisfiedLinkError ule2) {
|
||||
// If extraction failed, extract
|
||||
try (InputStream resIs = m_loadClass.getResourceAsStream(resname)) {
|
||||
if (resIs == null) {
|
||||
throw new IOException(getLoadErrorMessage(ule));
|
||||
}
|
||||
jniLibrary.getParentFile().mkdirs();
|
||||
try (OutputStream os = Files.newOutputStream(jniLibrary.toPath())) {
|
||||
byte[] buffer = new byte[0xFFFF]; // 64K copy buffer
|
||||
int readBytes;
|
||||
while ((readBytes = resIs.read(buffer)) != -1) { // NOPMD
|
||||
os.write(buffer, 0, readBytes);
|
||||
}
|
||||
}
|
||||
System.load(jniLibrary.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
wpiutil/src/main/java/edu/wpi/first/util/WPIUtilJNI.java
Normal file
66
wpiutil/src/main/java/edu/wpi/first/util/WPIUtilJNI.java
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public final class WPIUtilJNI {
|
||||
static boolean libraryLoaded = false;
|
||||
static RuntimeLoader<WPIUtilJNI> loader = null;
|
||||
|
||||
public static class Helper {
|
||||
private static AtomicBoolean extractOnStaticLoad = new AtomicBoolean(true);
|
||||
|
||||
public static boolean getExtractOnStaticLoad() {
|
||||
return extractOnStaticLoad.get();
|
||||
}
|
||||
|
||||
public static void setExtractOnStaticLoad(boolean load) {
|
||||
extractOnStaticLoad.set(load);
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
if (Helper.getExtractOnStaticLoad()) {
|
||||
try {
|
||||
loader =
|
||||
new RuntimeLoader<>(
|
||||
"wpiutiljni", RuntimeLoader.getDefaultExtractionRoot(), WPIUtilJNI.class);
|
||||
loader.loadLibrary();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
libraryLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force load the library.
|
||||
*
|
||||
* @throws IOException if the library failed to load
|
||||
*/
|
||||
public static synchronized void forceLoad() throws IOException {
|
||||
if (libraryLoaded) {
|
||||
return;
|
||||
}
|
||||
loader =
|
||||
new RuntimeLoader<>(
|
||||
"wpiutiljni", RuntimeLoader.getDefaultExtractionRoot(), WPIUtilJNI.class);
|
||||
loader.loadLibrary();
|
||||
libraryLoaded = true;
|
||||
}
|
||||
|
||||
public static native void enableMockTime();
|
||||
|
||||
public static native void setMockTime(long time);
|
||||
|
||||
public static native long now();
|
||||
|
||||
public static native void addPortForwarder(int port, String remoteHost, int remotePort);
|
||||
|
||||
public static native void removePortForwarder(int port);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
package edu.wpi.first.util.net;
|
||||
|
||||
import edu.wpi.first.util.WPIUtilJNI;
|
||||
|
||||
/**
|
||||
* Forward ports to another host. This is primarily useful for accessing Ethernet-connected devices
|
||||
* from a computer tethered to the RoboRIO USB port.
|
||||
*/
|
||||
public final class PortForwarder {
|
||||
private PortForwarder() {
|
||||
throw new UnsupportedOperationException("This is a utility class!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward a local TCP port to a remote host and port. Note that local ports less than 1024 won't
|
||||
* work as a normal user.
|
||||
*
|
||||
* @param port local port number
|
||||
* @param remoteHost remote IP address / DNS name
|
||||
* @param remotePort remote port number
|
||||
*/
|
||||
public static void add(int port, String remoteHost, int remotePort) {
|
||||
WPIUtilJNI.addPortForwarder(port, remoteHost, remotePort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop TCP forwarding on a port.
|
||||
*
|
||||
* @param port local port number
|
||||
*/
|
||||
public static void remove(int port) {
|
||||
WPIUtilJNI.removePortForwarder(port);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user