Skip to content

Commit 35a5184

Browse files
Merge branch 'foreign-function-api-upstream' into foreign-api-jdk-23
Conflicts: .github/workflows/test.yml src/main/java/dev/dirs/ProjectDirectories.java src/main/java/dev/dirs/impl/Windows.java src/test/java/dev/dirs/impl/UtilTest.java
2 parents edeeaa5 + 9cb2676 commit 35a5184

File tree

7 files changed

+174
-183
lines changed

7 files changed

+174
-183
lines changed

build.sc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@ object directories extends JavaModule with PublishModule {
3434
else value
3535
}
3636

37-
def javacOptions = super.javacOptions() ++ Seq(
38-
"--release", "8"
39-
)
4037
def javadocOptions = super.javadocOptions() ++ Seq(
4138
"-Xdoclint:none"
4239
)

src/main/java/dev/dirs/GetWinDirs.java

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/main/java/dev/dirs/ProjectDirectories.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import dev.dirs.impl.Util;
66
import dev.dirs.impl.Windows;
77

8+
import java.util.function.Supplier;
89
import java.util.Objects;
910

1011
/** {@code ProjectDirectories} computes the location of cache, config or data directories for a specific application,
@@ -228,10 +229,10 @@ private ProjectDirectories(
228229
* @return A new {@code ProjectDirectories} instance, whose directory field values are directly derived from the {@code path} argument.
229230
*/
230231
public static ProjectDirectories fromPath(String path) {
231-
return fromPath(path, GetWinDirs.powerShellBased);
232+
return fromPath(path, Windows.getDefaultSupplier());
232233
}
233234

234-
public static ProjectDirectories fromPath(String path, GetWinDirs getWinDirs) {
235+
public static ProjectDirectories fromPath(String path, Supplier<Windows> windows) {
235236
String homeDir;
236237
String cacheDir;
237238
String configDir;
@@ -262,7 +263,7 @@ public static ProjectDirectories fromPath(String path, GetWinDirs getWinDirs) {
262263
preferenceDir = homeDir + "/Library/Preferences/" + path;
263264
break;
264265
case Constants.WIN:
265-
String[] winDirs = getWinDirs.getWinDirs("3EB685DB-65F9-4CF6-A03A-E3EF65729F3D", "F1B32785-6FBA-4FCF-9D55-7B8E7F157091");
266+
String[] winDirs = windows.get().winDirs("3EB685DB-65F9-4CF6-A03A-E3EF65729F3D", "F1B32785-6FBA-4FCF-9D55-7B8E7F157091");
266267
String appDataRoaming = winDirs[0] + '\\' + path;
267268
String appDataLocal = winDirs[1] + '\\' + path;
268269
dataDir = appDataRoaming + "\\data";
@@ -298,10 +299,10 @@ public static ProjectDirectories fromPath(String path, GetWinDirs getWinDirs) {
298299
* {@code qualifier}, {@code organization} and {@code application} arguments.
299300
*/
300301
public static ProjectDirectories from(String qualifier, String organization, String application) {
301-
return from(qualifier, organization, application, GetWinDirs.powerShellBased);
302+
return from(qualifier, organization, application, Windows.getDefaultSupplier());
302303
}
303304

304-
public static ProjectDirectories from(String qualifier, String organization, String application, GetWinDirs getWinDirs) {
305+
public static ProjectDirectories from(String qualifier, String organization, String application, Supplier<Windows> windows) {
305306
if (Util.isNullOrEmpty(organization) && Util.isNullOrEmpty(application))
306307
throw new UnsupportedOperationException("organization and application arguments cannot both be null/empty");
307308
String path;
@@ -322,7 +323,7 @@ public static ProjectDirectories from(String qualifier, String organization, Str
322323
default:
323324
throw new UnsupportedOperatingSystemException("Project directories are not supported on " + Constants.operatingSystemName);
324325
}
325-
return fromPath(path, getWinDirs);
326+
return fromPath(path, windows);
326327
}
327328

328329
@Override
Lines changed: 12 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,164 +1,8 @@
11
package dev.dirs.impl;
22

3-
import dev.dirs.Constants;
3+
import java.util.function.Supplier;
44

5-
import java.io.BufferedReader;
6-
import java.io.File;
7-
import java.io.IOException;
8-
import java.io.InputStreamReader;
9-
import java.lang.reflect.Method;
10-
import java.nio.charset.Charset;
11-
12-
public class Windows {
13-
14-
private Windows() {}
15-
16-
private static final String UTF8_BOM = "\ufeff";
17-
private static Object base64Encoder = null;
18-
private static Method base64EncodeMethod = null;
19-
// This string needs to end up being a multiple of 3 bytes after conversion to UTF-16. (It is currently 1200 bytes.)
20-
// This is because Base64 converts 3 bytes to 4 letters; other numbers of bytes would introduce padding, which
21-
// would make it harder to simply concatenate this precomputed string with whatever directories the user requests.
22-
static final String SCRIPT_START_BASE64 = Constants.operatingSystem == 'w' ? toUTF16LEBase64("& {\n" +
23-
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n" +
24-
"Add-Type @\"\n" +
25-
"using System;\n" +
26-
"using System.Runtime.InteropServices;\n" +
27-
"public class Dir {\n" +
28-
" [DllImport(\"shell32.dll\")]\n" +
29-
" private static extern int SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken, out IntPtr pszPath);\n" +
30-
" public static string GetKnownFolderPath(string rfid) {\n" +
31-
" IntPtr pszPath;\n" +
32-
" if (SHGetKnownFolderPath(new Guid(rfid), 0, IntPtr.Zero, out pszPath) != 0) return \"\";\n" +
33-
" string path = Marshal.PtrToStringUni(pszPath);\n" +
34-
" Marshal.FreeCoTaskMem(pszPath);\n" +
35-
" return path;\n" +
36-
" }\n" +
37-
"}\n" +
38-
"\"@\n") : null;
39-
40-
public static String[] getWinDirs(String... guids) {
41-
int guidsLength = guids.length;
42-
StringBuilder buf = new StringBuilder(guidsLength * 68);
43-
for (int i = 0; i < guidsLength; i++) {
44-
buf.append("[Dir]::GetKnownFolderPath(\"");
45-
buf.append(guids[i]);
46-
buf.append("\")\n");
47-
}
48-
49-
String encodedCommand = SCRIPT_START_BASE64 + toUTF16LEBase64(buf + "}");
50-
String path = System.getenv("Path");
51-
String[] dirs = path == null ? new String[0] : path.split(File.pathSeparator);
52-
if (dirs.length == 0) {
53-
return windowsFallback(guidsLength, encodedCommand);
54-
}
55-
try {
56-
return runWinCommands(guidsLength, dirs, encodedCommand);
57-
} catch (IOException e) {
58-
return windowsFallback(guidsLength, encodedCommand);
59-
}
60-
}
61-
62-
private static String toUTF16LEBase64(String script) {
63-
byte[] scriptInUtf16LEBytes = script.getBytes(Charset.forName("UTF-16LE"));
64-
if (base64EncodeMethod == null) {
65-
initBase64Encoding();
66-
}
67-
try {
68-
return (String) base64EncodeMethod.invoke(base64Encoder, scriptInUtf16LEBytes);
69-
} catch (Exception e) {
70-
throw new RuntimeException("Base64 encoding failed!", e);
71-
}
72-
}
73-
74-
private static void initBase64Encoding() {
75-
try {
76-
base64Encoder = Class.forName("java.util.Base64").getMethod("getEncoder").invoke(null);
77-
base64EncodeMethod = base64Encoder.getClass().getMethod("encodeToString", byte[].class);
78-
} catch (Exception e1) {
79-
try {
80-
base64EncodeMethod = Class.forName("sun.misc.BASE64Encoder").getMethod("encode", byte[].class);
81-
} catch (Exception e2) {
82-
throw new RuntimeException(
83-
"Could not find any viable Base64 encoder! (java.util.Base64 failed with: " + e1.getMessage() + ")", e2);
84-
}
85-
}
86-
}
87-
88-
private static String[] runCommands(int expectedResultLines, Charset charset, String... commands) throws IOException {
89-
final Process process = new ProcessBuilder(commands).start();
90-
91-
String[] results = new String[expectedResultLines];
92-
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), charset));
93-
try {
94-
for (int i = 0; i < expectedResultLines; i++) {
95-
String line = reader.readLine();
96-
if (i == 0 && Constants.operatingSystem == 'w' && line != null && line.startsWith(UTF8_BOM))
97-
line = line.substring(UTF8_BOM.length());
98-
results[i] = line;
99-
}
100-
return results;
101-
} finally {
102-
process.destroy();
103-
try {
104-
reader.close();
105-
} catch (IOException e) {
106-
e.printStackTrace();
107-
}
108-
}
109-
}
110-
111-
private static String[] runWinCommands(int guidsLength, String[] dirs, String encodedCommand) throws IOException {
112-
// legacy powershell.exe seems to run faster than pwsh.exe so prefer it if available
113-
String[] commands = { "powershell.exe", "pwsh.exe" };
114-
IOException firstException = null;
115-
for (String dir : dirs) {
116-
for (String command : commands) {
117-
File commandFile = new File(dir, command);
118-
if (commandFile.exists()) {
119-
try {
120-
return runCommands(guidsLength, Charset.forName("UTF-8"),
121-
commandFile.toString(),
122-
"-NoProfile",
123-
"-EncodedCommand",
124-
encodedCommand
125-
);
126-
} catch (IOException e) {
127-
firstException = firstException == null ? e : firstException;
128-
}
129-
}
130-
}
131-
}
132-
if (firstException != null) {
133-
throw firstException;
134-
}
135-
else throw new IOException("no directories");
136-
}
137-
138-
private static String[] windowsFallback(int guidsLength, String encodedCommand) {
139-
File powerShellBase = new File("C:\\Program Files\\Powershell");
140-
String[] powerShellDirs = powerShellBase.list();
141-
if (powerShellDirs == null) {
142-
powerShellDirs = new String[0];
143-
}
144-
String[] allPowerShellDirs = new String[powerShellDirs.length + 1];
145-
146-
// legacy powershell.exe seems to run faster than pwsh.exe so prefer it if available
147-
String systemRoot = System.getenv("SystemRoot");
148-
if (systemRoot == null) {
149-
systemRoot = "C:\\Windows";
150-
}
151-
allPowerShellDirs[0] = systemRoot + "\\System32\\WindowsPowerShell\\v1.0\\";
152-
153-
for (int i = 0; i < powerShellDirs.length; ++i) {
154-
allPowerShellDirs[i + 1] = new File(powerShellBase, powerShellDirs[i]).toString();
155-
}
156-
try {
157-
return runWinCommands(guidsLength, allPowerShellDirs, encodedCommand);
158-
} catch (final IOException ex) {
159-
throw new RuntimeException("Couldn't find pwsh.exe or powershell.exe on path or in default system locations", ex);
160-
}
161-
}
5+
public interface Windows {
1626

1637
public static String applicationPath(String qualifier, String organization, String application) {
1648
StringBuilder buf = new StringBuilder(Math.max(Util.stringLength(organization) + Util.stringLength(application), 0));
@@ -173,4 +17,14 @@ public static String applicationPath(String qualifier, String organization, Stri
17317
buf.append(application);
17418
return buf.toString();
17519
}
20+
21+
String[] winDirs(String... folderIds);
22+
23+
public static String[] getWinDirs(String... folderIds) {
24+
return getDefaultSupplier().get().winDirs(folderIds);
25+
}
26+
27+
public static Supplier<Windows> getDefaultSupplier() {
28+
return () -> WindowsDefault.getDefaultImpl();
29+
}
17630
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dev.dirs.impl;
2+
3+
final class WindowsDefault {
4+
5+
static Windows getDefaultImpl() {
6+
return new WindowsForeign();
7+
}
8+
9+
}

0 commit comments

Comments
 (0)