lwjgl/src/java/org/lwjgl/LWJGLUtil.java

444 lines
15 KiB
Java

/*
* Copyright (c) 2002-2008 LWJGL Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'LWJGL' nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.lwjgl;
import java.io.File;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.util.*;
import dev.pfaff.unfettered.UnsafeUtil;
import static java.lang.invoke.MethodType.methodType;
/**
* <p>
* Internal library methods
* </p>
*
* @author Brian Matzon <brian@matzon.dk>
* @version $Revision$
* $Id$
*/
public class LWJGLUtil {
public static final int PLATFORM_LINUX = 1;
public static final int PLATFORM_MACOSX = 2;
public static final int PLATFORM_WINDOWS = 3;
public static final String PLATFORM_LINUX_NAME = "linux";
public static final String PLATFORM_MACOSX_NAME = "macosx";
public static final String PLATFORM_WINDOWS_NAME = "windows";
private static final MethodHandle MH_findLibrary;
static {
MethodHandles.Lookup l = UnsafeUtil.getTrustedLookup();
try {
MH_findLibrary = l.findVirtual(ClassLoader.class, "findLibrary", methodType(String.class, String.class));
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to get a MethodHandle for the findLibrary method", e);
}
}
/** LWJGL Logo - 16 by 16 pixels */
public static final ByteBuffer LWJGLIcon16x16 = loadIcon(LWJGLIconData.ICON_16x16);
/** LWJGL Logo - 32 by 32 pixels */
public static final ByteBuffer LWJGLIcon32x32 = loadIcon(LWJGLIconData.ICON_32x32);
/** Debug flag. */
public static final boolean DEBUG = getPrivilegedBoolean("org.lwjgl.util.Debug");
public static final boolean CHECKS = !getPrivilegedBoolean("org.lwjgl.util.NoChecks");
private static final int PLATFORM;
private static StupidSimpleLogger logger = msg -> {
//if (DEBUG) {
System.err.println("[LWJGL] " + msg.get());
//}
};
public static StupidSimpleLogger logger() {
return logger;
}
public static void logger(StupidSimpleLogger newLogger) {
logger = newLogger;
}
/**
* Logs the given message.
*
* @param msg Message to print
*
* @deprecated Use {@link #logger} instead.
*/
@Deprecated
public static void log(CharSequence msg) {
logger().log(() -> msg.toString());
}
static {
final String osName = getPrivilegedProperty("os.name");
if ( osName.startsWith("Windows") )
PLATFORM = PLATFORM_WINDOWS;
else if ( osName.startsWith("Linux") || osName.startsWith("FreeBSD") || osName.startsWith("OpenBSD") || osName.startsWith("SunOS") || osName.startsWith("Unix") )
PLATFORM = PLATFORM_LINUX;
else if ( osName.startsWith("Mac OS X") || osName.startsWith("Darwin") )
PLATFORM = PLATFORM_MACOSX;
else
throw new LinkageError("Unknown platform: " + osName);
}
private static ByteBuffer loadIcon(String data) {
int len = data.length();
ByteBuffer bb = BufferUtils.createByteBuffer(len);
for(int i=0 ; i<len ; i++) {
bb.put(i, (byte)data.charAt(i));
}
return bb.asReadOnlyBuffer();
}
/**
* @see #PLATFORM_WINDOWS
* @see #PLATFORM_LINUX
* @see #PLATFORM_MACOSX
* @return the current platform type
*/
public static int getPlatform() {
return PLATFORM;
}
/**
* @see #PLATFORM_WINDOWS_NAME
* @see #PLATFORM_LINUX_NAME
* @see #PLATFORM_MACOSX_NAME
* @return current platform name
*/
public static String getPlatformName() {
switch (LWJGLUtil.getPlatform()) {
case LWJGLUtil.PLATFORM_LINUX:
return PLATFORM_LINUX_NAME;
case LWJGLUtil.PLATFORM_MACOSX:
return PLATFORM_MACOSX_NAME;
case LWJGLUtil.PLATFORM_WINDOWS:
return PLATFORM_WINDOWS_NAME;
default:
return "unknown";
}
}
/**
* Wraps {@link System#mapLibraryName}. On OS X with JDK 6, the .jnilib file
* extension will be replaced with .dylib.
*
* @param name the name of the library.
*
* @return a platform-dependent native library name.
*/
public static String mapLibraryName(String name) {
String libName = System.mapLibraryName(name);
return LWJGLUtil.getPlatform() == LWJGLUtil.PLATFORM_MACOSX && libName.endsWith(".jnilib")
? libName.substring(0, libName.length() - ".jnilib".length()) + ".dylib"
: libName;
}
/**
* Locates the paths required by a library.
*
* @param libname Local Library Name to search the classloader with ("openal").
* @param platform_lib_name The native library name ("libopenal.so")
* @param classloader The classloader to ask for library paths
* @return Paths to located libraries, if any
*/
public static String[] getLibraryPaths(String libname, String platform_lib_name, ClassLoader classloader) {
return getLibraryPaths(libname, new String[]{platform_lib_name}, classloader);
}
/**
* Locates the paths required by a library.
*
* @param libname Local Library Name to search the classloader with ("openal").
* @param platform_lib_names The list of possible library names ("libopenal.so")
* @param classloader The classloader to ask for library paths
* @return Paths to located libraries, if any
*/
public static String[] getLibraryPaths(String libname, String[] platform_lib_names, ClassLoader classloader) {
// need to pass path of possible locations of library to native side
List<String> possible_paths = new ArrayList<String>();
String classloader_path = getPathFromClassLoader(libname, classloader);
if (classloader_path != null) {
if (DEBUG) {
logger().log(() -> "getPathFromClassLoader: Path found: " + classloader_path);
}
possible_paths.add(classloader_path);
}
for ( String platform_lib_name : platform_lib_names ) {
String lwjgl_classloader_path = getPathFromClassLoader("lwjgl", classloader);
if ( lwjgl_classloader_path != null ) {
if ( DEBUG ) {
logger().log(() -> "getPathFromClassLoader: Path found: " + lwjgl_classloader_path);
}
possible_paths.add(lwjgl_classloader_path.substring(0, lwjgl_classloader_path.lastIndexOf(File.separator))
+ File.separator + platform_lib_name);
}
// add Installer path
String alternative_path = getPrivilegedProperty("org.lwjgl.librarypath");
if ( alternative_path != null ) {
possible_paths.add(alternative_path + File.separator + platform_lib_name);
}
// Add all possible paths from java.library.path
String java_library_path = getPrivilegedProperty("java.library.path");
StringTokenizer st = new StringTokenizer(java_library_path, File.pathSeparator);
while ( st.hasMoreTokens() ) {
String path = st.nextToken();
possible_paths.add(path + File.separator + platform_lib_name);
}
// TODO: this can be very dangerous (see recent (2022-08) use of completely safe notepad.exe to load a malicious dll)
//add current path
String current_dir = getPrivilegedProperty("user.dir");
possible_paths.add(current_dir + File.separator + platform_lib_name);
//add pure library (no path, let OS search)
possible_paths.add(platform_lib_name);
}
//create needed string array
return possible_paths.toArray(new String[possible_paths.size()]);
}
static void execPrivileged(String... cmd_array) throws Exception {
Process process = Runtime.getRuntime().exec(cmd_array);
// Close unused streams to make sure the child process won't hang
process.getInputStream().close();
process.getOutputStream().close();
process.getErrorStream().close();
}
private static String getPrivilegedProperty(String property_name) {
return System.getProperty(property_name);
}
/** Gets a boolean property as a privileged action. */
public static boolean getPrivilegedBoolean(String property_name) {
String s = getPrivilegedProperty(property_name);
if (s == null || s.equals("false")) return false;
if (DEBUG) {
if (!s.equals("true")) {
logger().log(() -> "Value of boolean property " + property_name + " is not one of [true, false]: " + s);
}
}
return true;
}
/** Gets a string property as a privileged action. */
public static String getPrivilegedString(String property_name) {
return getPrivilegedProperty(property_name);
}
/**
* Gets an integer property as a privileged action.
*
* @param property_name the integer property name
*
* @return the property value
*/
public static Integer getPrivilegedInteger(final String property_name) {
String s = getPrivilegedProperty(property_name);
if (s == null) return null;
return Integer.decode(s);
}
/**
* Gets an integer property as a privileged action.
*
* @param property_name the integer property name
* @param default_val the default value to use if the property is not defined
*
* @return the property value
*/
public static int getPrivilegedInteger(final String property_name, final int default_val) {
String s = getPrivilegedProperty(property_name);
if (s == null) return default_val;
return Integer.decode(s);
}
/**
* Tries to locate named library from the current ClassLoader
* This method exists because native libraries are loaded from native code, and as such
* is exempt from ClassLoader library loading routines. It therefore always fails.
* We therefore invoke the protected method of the ClassLoader to see if it can
* locate it.
*
* @param libname Name of library to search for
* @param classLoader Classloader to use
* @return Absolute path to library if found, otherwise null
*/
private static String getPathFromClassLoader(final String libname, final ClassLoader classLoader) {
if (DEBUG) {
logger().log(() -> "getPathFromClassLoader: Searching for '" + libname + '\'');
}
try {
return (String) MH_findLibrary.invokeExact(classLoader, libname);
} catch (Throwable e) {
logger().log(() -> "getPathFromClassLoader: Failure locating using classloader '" + classLoader + '\'', e);
return null;
}
}
/**
* Method to determine if the current system is running a version of
* Mac OS X better than the given version. This is only useful for Mac OS X
* specific code and will not work for any other platform.
*/
public static boolean isMacOSXEqualsOrBetterThan(int major_required, int minor_required) {
String os_version = getPrivilegedProperty("os.version");
int dotI = os_version.indexOf('.');
if (dotI == -1) {
if (DEBUG) {
logger().log("No '.' delimiter was found in value of 'os.version'");
}
return false;
}
int major;
int minor;
try {
major = Integer.parseInt(os_version, 0, dotI, 10);
minor = Integer.parseInt(os_version, dotI + 1, os_version.length(), 10);
} catch (Exception e) {
logger().log("Exception occurred while trying to determine OS version", e);
// Best guess, no
return false;
}
return major > major_required || (major == major_required && minor >= minor_required);
}
/**
* Returns a map of public static final integer fields in the specified classes, to their String representations.
* An optional filter can be specified to only include specific fields. The target map may be null, in which
* case a new map is allocated and returned.
* <p>
* This method is useful when debugging to quickly identify values returned from the AL/GL/CL APIs.
*
* @param filter the filter to use (optional)
* @param target the target map (optional)
* @param tokenClasses an array of classes to get tokens from
*
* @return the token map
*/
public static Map<Integer, String> getClassTokens(final TokenFilter filter, final Map<Integer, String> target, final Class ... tokenClasses) {
return getClassTokens(filter, target, Arrays.asList(tokenClasses));
}
/**
* Returns a map of public static final integer fields in the specified classes, to their String representations.
* An optional filter can be specified to only include specific fields. The target map may be null, in which
* case a new map is allocated and returned.
* <p>
* This method is useful when debugging to quickly identify values returned from the AL/GL/CL APIs.
*
* @param filter the filter to use (optional)
* @param target the target map (optional)
* @param tokenClasses the classes to get tokens from
*
* @return the token map
*/
public static Map<Integer, String> getClassTokens(final TokenFilter filter, Map<Integer, String> target, final Iterable<Class> tokenClasses) {
if ( target == null )
target = new HashMap<Integer, String>();
final int TOKEN_MODIFIERS = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL;
for ( final Class tokenClass : tokenClasses ) {
for ( final Field field : tokenClass.getDeclaredFields() ) {
// Get only <public static final int> fields.
if ( (field.getModifiers() & TOKEN_MODIFIERS) == TOKEN_MODIFIERS && field.getType() == int.class ) {
try {
final int value = field.getInt(null);
if ( filter != null && !filter.accept(field, value) )
continue;
if ( target.containsKey(value) ) // Print colliding tokens in their hex representation.
target.put(value, toHexString(value));
else
target.put(value, field.getName());
} catch (IllegalAccessException e) {
// Ignore
}
}
}
}
return target;
}
/**
* Returns a string representation of the integer argument as an
* unsigned integer in base&nbsp;16. The string will be uppercase
* and will have a leading '0x'.
*
* @param value the integer value
*
* @return the hex string representation
*/
public static String toHexString(final int value) {
return "0x" + Integer.toHexString(value).toUpperCase();
}
/** Simple interface for Field filtering. */
@FunctionalInterface
public interface TokenFilter {
/**
* Should return true if the specified Field passes the filter.
*
* @param field the Field to test
* @param value the integer value of the field
*
* @return true if the Field is accepted
*/
boolean accept(Field field, int value);
}
}