forked from dirs-dev/directories-jvm
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from coursier/foreign-api-jdk-23
Add upstream repo support for Windows API calls via JDK foreign API stuff
- Loading branch information
Showing
15 changed files
with
593 additions
and
184 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package dev.dirs.jni; | ||
|
||
import coursier.jniutils.WindowsKnownFolders; | ||
import dev.dirs.impl.Windows; | ||
|
||
import java.util.function.Supplier; | ||
|
||
public final class WindowsJni implements Windows { | ||
|
||
public String[] winDirs(String... folderIds) { | ||
String[] dirs = new String[folderIds.length]; | ||
for (int i = 0; i < folderIds.length; i++) { | ||
dirs[i] = WindowsKnownFolders.knownFolderPath("{" + folderIds[i] + "}"); | ||
} | ||
return dirs; | ||
} | ||
|
||
public static Supplier<Windows> getJdkAwareSupplier() { | ||
String javaVersion = System.getProperty("java.version", "0"); | ||
if (javaVersion.substring(0, "1.".length()).equals("1.")) | ||
javaVersion = javaVersion.substring("1.".length()); | ||
int dotIdx = javaVersion.indexOf('.'); | ||
if (dotIdx >= 0) | ||
javaVersion = javaVersion.substring(0, dotIdx); | ||
int jdkVersion = Integer.parseInt(javaVersion); | ||
if (jdkVersion >= 23) | ||
return Windows.getDefaultSupplier(); | ||
else | ||
return () -> new WindowsJni(); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package dev.dirs.impl; | ||
|
||
import java.util.function.Supplier; | ||
|
||
final class WindowsDefault { | ||
|
||
static Supplier<Windows> getDefaultSupplier() { | ||
return () -> new WindowsForeign(); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package dev.dirs.impl; | ||
|
||
import dev.dirs.Constants; | ||
|
||
import java.lang.foreign.AddressLayout; | ||
import java.lang.foreign.Arena; | ||
import java.lang.foreign.FunctionDescriptor; | ||
import java.lang.foreign.GroupLayout; | ||
import java.lang.foreign.Linker; | ||
import java.lang.foreign.MemoryLayout; | ||
import java.lang.foreign.MemorySegment; | ||
import java.lang.foreign.SymbolLookup; | ||
import java.lang.foreign.ValueLayout; | ||
import java.lang.invoke.MethodHandle; | ||
|
||
import static java.lang.foreign.ValueLayout.JAVA_BYTE; | ||
import static java.lang.foreign.ValueLayout.JAVA_CHAR; | ||
|
||
public final class WindowsForeign implements Windows { | ||
|
||
public WindowsForeign() {} | ||
|
||
static { | ||
if (Constants.operatingSystem == 'w') { | ||
System.loadLibrary("ole32"); | ||
System.loadLibrary("shell32"); | ||
} | ||
} | ||
|
||
private static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.loaderLookup().or(Linker.nativeLinker().defaultLookup()); | ||
private static final ValueLayout.OfByte C_CHAR = ValueLayout.JAVA_BYTE; | ||
private static final ValueLayout.OfShort C_SHORT = ValueLayout.JAVA_SHORT; | ||
private static final AddressLayout C_POINTER = ValueLayout.ADDRESS | ||
.withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, JAVA_BYTE)); | ||
private static final ValueLayout.OfInt C_LONG = ValueLayout.JAVA_INT; | ||
|
||
private static String getDir(String folderId) { | ||
try (var arena = Arena.ofConfined()) { | ||
MemorySegment guidSegment = arena.allocate(GUID_LAYOUT); | ||
if (CLSIDFromString(createSegmentFromString(folderId, arena), guidSegment) != 0) { | ||
throw new AssertionError("failed converting string " + folderId + " to KnownFolderId"); | ||
} | ||
MemorySegment path = arena.allocate(C_POINTER); | ||
SHGetKnownFolderPath(guidSegment, 0, MemorySegment.NULL, path); | ||
return createStringFromSegment(path.get(C_POINTER, 0)); | ||
} | ||
} | ||
|
||
public String[] winDirs(String... folderIds) { | ||
String[] values = new String[folderIds.length]; | ||
for (int i = 0; i < folderIds.length; i += 1) | ||
values[i] = getDir("{" + folderIds[i] + "}"); | ||
return values; | ||
} | ||
|
||
/** | ||
* Creates a memory segment as a copy of a Java string. | ||
* <p> | ||
* The memory segment contains a copy of the string (null-terminated, UTF-16/wide characters). | ||
* </p> | ||
* | ||
* @param str the string to copy | ||
* @param arena the arena for the memory segment | ||
* @return the resulting memory segment | ||
*/ | ||
private static MemorySegment createSegmentFromString(String str, Arena arena) { | ||
// allocate segment (including space for terminating null) | ||
var segment = arena.allocate(JAVA_CHAR, str.length() + 1L); | ||
// copy characters | ||
segment.copyFrom(MemorySegment.ofArray(str.toCharArray())); | ||
return segment; | ||
} | ||
|
||
/** | ||
* Creates a copy of the string in the memory segment. | ||
* <p> | ||
* The string must be a null-terminated UTF-16 (wide character) string. | ||
* </p> | ||
* | ||
* @param segment the memory segment | ||
* @return copied string | ||
*/ | ||
private static String createStringFromSegment(MemorySegment segment) { | ||
var len = 0; | ||
while (segment.get(JAVA_CHAR, len) != 0) { | ||
len += 2; | ||
} | ||
|
||
return new String(segment.asSlice(0, len).toArray(JAVA_CHAR)); | ||
} | ||
|
||
private static MemorySegment findOrThrow(String symbol) { | ||
return SYMBOL_LOOKUP.find(symbol) | ||
.orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol: " + symbol)); | ||
} | ||
|
||
private static final GroupLayout GUID_LAYOUT = MemoryLayout.structLayout( | ||
C_LONG.withName("Data1"), | ||
C_SHORT.withName("Data2"), | ||
C_SHORT.withName("Data3"), | ||
MemoryLayout.sequenceLayout(8, C_CHAR).withName("Data4")) | ||
.withName("_GUID"); | ||
|
||
/** | ||
* {@snippet lang=c : | ||
* extern HRESULT CLSIDFromString(LPCOLESTR lpsz, LPCLSID pclsid) | ||
* } | ||
*/ | ||
private static int CLSIDFromString(MemorySegment lpsz, MemorySegment pclsid) { | ||
var handle = CLSIDFromString.HANDLE; | ||
try { | ||
return (int) handle.invokeExact(lpsz, pclsid); | ||
} catch (Throwable throwable) { | ||
throw new AssertionError("failed to invoke `CLSIDFromString`", throwable); | ||
} | ||
} | ||
|
||
private static class CLSIDFromString { | ||
public static final FunctionDescriptor DESC = FunctionDescriptor.of(C_LONG, C_POINTER, C_POINTER); | ||
|
||
public static final MethodHandle HANDLE = Linker.nativeLinker() | ||
.downcallHandle(findOrThrow("CLSIDFromString"), DESC); | ||
} | ||
|
||
/** | ||
* {@snippet lang=c : | ||
* extern HRESULT SHGetKnownFolderPath(const KNOWNFOLDERID *const rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath) | ||
* } | ||
*/ | ||
private static int SHGetKnownFolderPath(MemorySegment rfid, int dwFlags, MemorySegment hToken, MemorySegment ppszPath) { | ||
var handle = SHGetKnownFolderPath.HANDLE; | ||
try { | ||
return (int) handle.invokeExact(rfid, dwFlags, hToken, ppszPath); | ||
} catch (Throwable throwable) { | ||
throw new AssertionError("failed to invoke `SHGetKnownFolderPath`", throwable); | ||
} | ||
} | ||
|
||
private static class SHGetKnownFolderPath { | ||
public static final FunctionDescriptor DESC = FunctionDescriptor.of(C_LONG, C_POINTER, C_LONG, C_POINTER, C_POINTER); | ||
|
||
public static final MethodHandle HANDLE = Linker.nativeLinker() | ||
.downcallHandle(findOrThrow("SHGetKnownFolderPath"), DESC); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.