diff --git a/src/main/java/org/fusesource/jansi/AnsiConsole.java b/src/main/java/org/fusesource/jansi/AnsiConsole.java index 749c39cb..0e1d5df3 100644 --- a/src/main/java/org/fusesource/jansi/AnsiConsole.java +++ b/src/main/java/org/fusesource/jansi/AnsiConsole.java @@ -31,6 +31,9 @@ import org.fusesource.jansi.io.AnsiProcessor; import org.fusesource.jansi.io.FastBufferedOutputStream; +import static org.fusesource.jansi.internal.AnsiConsoleSupportHolder.getCLibrary; +import static org.fusesource.jansi.internal.AnsiConsoleSupportHolder.getKernel32; + /** * Provides consistent access to an ANSI aware console PrintStream or an ANSI codes stripping PrintStream * if not on a terminal (see @@ -155,10 +158,21 @@ public class AnsiConsole { */ public static final String JANSI_GRACEFUL = "jansi.graceful"; + /** + * The {@code jansi.providers} system property can be set to control which internal provider + * will be used. If this property is not set, the {@code ffm} provider will be used if available, + * else the {@code jni} one will be used. If set, this property is interpreted as a comma + * separated list of provider names to try in order. + */ public static final String JANSI_PROVIDERS = "jansi.providers"; + /** + * The name of the {@code jni} provider. + */ public static final String JANSI_PROVIDER_JNI = "jni"; + /** + * The name of the {@code ffm} provider. + */ public static final String JANSI_PROVIDER_FFM = "ffm"; - public static final String JANSI_PROVIDERS_DEFAULT = JANSI_PROVIDER_FFM + "," + JANSI_PROVIDER_JNI; /** * @deprecated this field will be made private in a future release, use {@link #sysOut()} instead @@ -536,12 +550,4 @@ static synchronized void initStreams() { initialized = true; } } - - private static AnsiConsoleSupport.Kernel32 getKernel32() { - return AnsiConsoleSupport.getInstance().getKernel32(); - } - - private static AnsiConsoleSupport.CLibrary getCLibrary() { - return AnsiConsoleSupport.getInstance().getCLibrary(); - } } diff --git a/src/main/java/org/fusesource/jansi/AnsiConsoleSupportHolder.java b/src/main/java/org/fusesource/jansi/AnsiConsoleSupportHolder.java deleted file mode 100644 index 082f78d0..00000000 --- a/src/main/java/org/fusesource/jansi/AnsiConsoleSupportHolder.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2009-2023 the original author(s). - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.fusesource.jansi; - -import org.fusesource.jansi.internal.AnsiConsoleSupportJni; - -import static org.fusesource.jansi.AnsiConsole.JANSI_PROVIDERS; -import static org.fusesource.jansi.AnsiConsole.JANSI_PROVIDERS_DEFAULT; -import static org.fusesource.jansi.AnsiConsole.JANSI_PROVIDER_FFM; -import static org.fusesource.jansi.AnsiConsole.JANSI_PROVIDER_JNI; - -class AnsiConsoleSupportHolder { - static volatile AnsiConsoleSupport instance; - - static AnsiConsoleSupport get() { - if (instance == null) { - synchronized (AnsiConsoleSupportHolder.class) { - if (instance == null) { - instance = doGet(); - } - } - } - return instance; - } - - static AnsiConsoleSupport doGet() { - RuntimeException error = new RuntimeException("Unable to create AnsiConsoleSupport provider"); - String[] providers = - System.getProperty(JANSI_PROVIDERS, JANSI_PROVIDERS_DEFAULT).split(","); - for (String provider : providers) { - try { - if (JANSI_PROVIDER_FFM.equals(provider)) { - return (AnsiConsoleSupport) AnsiConsoleSupport.class - .getClassLoader() - .loadClass("org.fusesource.jansi.ffm.AnsiConsoleSupportFfm") - .getConstructor() - .newInstance(); - } else if (JANSI_PROVIDER_JNI.equals(provider)) { - return new AnsiConsoleSupportJni(); - } - } catch (Throwable t) { - error.addSuppressed(t); - } - } - throw error; - } -} diff --git a/src/main/java/org/fusesource/jansi/AnsiMain.java b/src/main/java/org/fusesource/jansi/AnsiMain.java index bb6ab3cc..e824322d 100644 --- a/src/main/java/org/fusesource/jansi/AnsiMain.java +++ b/src/main/java/org/fusesource/jansi/AnsiMain.java @@ -23,10 +23,11 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; -import java.nio.charset.StandardCharsets; import java.util.Properties; import org.fusesource.jansi.Ansi.Attribute; +import org.fusesource.jansi.internal.AnsiConsoleSupport; +import org.fusesource.jansi.internal.AnsiConsoleSupportHolder; import org.fusesource.jansi.internal.JansiLoader; import org.fusesource.jansi.internal.MingwSupport; @@ -56,9 +57,8 @@ public static void main(String... args) throws IOException { System.out.println(); - System.out.println("jansi.providers= " - + System.getProperty(AnsiConsole.JANSI_PROVIDERS, AnsiConsole.JANSI_PROVIDERS_DEFAULT)); - String provider = AnsiConsoleSupport.getInstance().getProviderName(); + System.out.println("jansi.providers= " + System.getProperty(AnsiConsole.JANSI_PROVIDERS, "")); + String provider = AnsiConsoleSupportHolder.getProviderName(); System.out.println("Selected provider: " + provider); if (AnsiConsole.JANSI_PROVIDER_JNI.equals(provider)) { @@ -204,8 +204,8 @@ private static void diagnoseTty(boolean stderr) { int isatty; int width; if (AnsiConsole.IS_WINDOWS) { - long console = AnsiConsoleSupport.getInstance().getKernel32().getStdHandle(!stderr); - isatty = AnsiConsoleSupport.getInstance().getKernel32().isTty(console); + long console = AnsiConsoleSupportHolder.getKernel32().getStdHandle(!stderr); + isatty = AnsiConsoleSupportHolder.getKernel32().isTty(console); if ((AnsiConsole.IS_CONEMU || AnsiConsole.IS_CYGWIN || AnsiConsole.IS_MSYSTEM) && isatty == 0) { MingwSupport mingw = new MingwSupport(); String name = mingw.getConsoleName(!stderr); @@ -217,12 +217,12 @@ private static void diagnoseTty(boolean stderr) { width = 0; } } else { - width = AnsiConsoleSupport.getInstance().getKernel32().getTerminalWidth(console); + width = AnsiConsoleSupportHolder.getKernel32().getTerminalWidth(console); } } else { int fd = stderr ? AnsiConsoleSupport.CLibrary.STDERR_FILENO : AnsiConsoleSupport.CLibrary.STDOUT_FILENO; - isatty = AnsiConsoleSupport.getInstance().getCLibrary().isTty(fd); - width = AnsiConsoleSupport.getInstance().getCLibrary().getTerminalWidth(fd); + isatty = AnsiConsoleSupportHolder.getCLibrary().isTty(fd); + width = AnsiConsoleSupportHolder.getCLibrary().getTerminalWidth(fd); } System.out.println("isatty(STD" + (stderr ? "ERR" : "OUT") + "_FILENO): " + isatty + ", System." diff --git a/src/main/java/org/fusesource/jansi/WindowsSupport.java b/src/main/java/org/fusesource/jansi/WindowsSupport.java index e14854cd..cfc0f9bc 100644 --- a/src/main/java/org/fusesource/jansi/WindowsSupport.java +++ b/src/main/java/org/fusesource/jansi/WindowsSupport.java @@ -15,18 +15,16 @@ */ package org.fusesource.jansi; +import org.fusesource.jansi.internal.AnsiConsoleSupportHolder; + public class WindowsSupport { public static String getLastErrorMessage() { - int errorCode = getKernel32().getLastError(); + int errorCode = AnsiConsoleSupportHolder.getKernel32().getLastError(); return getErrorMessage(errorCode); } public static String getErrorMessage(int errorCode) { - return getKernel32().getErrorMessage(errorCode); - } - - private static AnsiConsoleSupport.Kernel32 getKernel32() { - return AnsiConsoleSupport.getInstance().getKernel32(); + return AnsiConsoleSupportHolder.getKernel32().getErrorMessage(errorCode); } } diff --git a/src/main/java/org/fusesource/jansi/AnsiConsoleSupport.java b/src/main/java/org/fusesource/jansi/internal/AnsiConsoleSupport.java similarity index 91% rename from src/main/java/org/fusesource/jansi/AnsiConsoleSupport.java rename to src/main/java/org/fusesource/jansi/internal/AnsiConsoleSupport.java index 02907c55..4868207d 100644 --- a/src/main/java/org/fusesource/jansi/AnsiConsoleSupport.java +++ b/src/main/java/org/fusesource/jansi/internal/AnsiConsoleSupport.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.fusesource.jansi; +package org.fusesource.jansi.internal; import java.io.IOException; import java.io.OutputStream; @@ -56,8 +56,4 @@ interface Kernel32 { CLibrary getCLibrary(); Kernel32 getKernel32(); - - static AnsiConsoleSupport getInstance() { - return AnsiConsoleSupportHolder.get(); - } } diff --git a/src/main/java/org/fusesource/jansi/internal/AnsiConsoleSupportHolder.java b/src/main/java/org/fusesource/jansi/internal/AnsiConsoleSupportHolder.java new file mode 100644 index 00000000..eb049f98 --- /dev/null +++ b/src/main/java/org/fusesource/jansi/internal/AnsiConsoleSupportHolder.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2009-2023 the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.fusesource.jansi.internal; + +import static org.fusesource.jansi.AnsiConsole.JANSI_PROVIDERS; + +public final class AnsiConsoleSupportHolder { + + private static final String PROVIDER_NAME; + private static final AnsiConsoleSupport.CLibrary CLIBRARY; + private static final AnsiConsoleSupport.Kernel32 KERNEL32; + private static final Throwable ERR; + + private static AnsiConsoleSupport getDefaultProvider() { + try { + // Call the specialized constructor to check whether the module has native access enabled + // If not, fallback to JNI to avoid the JDK printing warnings in stderr + return (AnsiConsoleSupport) Class.forName("org.fusesource.jansi.internal.ffm.AnsiConsoleSupportImpl") + .getConstructor(boolean.class) + .newInstance(true); + } catch (Throwable ignored) { + } + + return new org.fusesource.jansi.internal.jni.AnsiConsoleSupportImpl(); + } + + private static AnsiConsoleSupport findProvider(String providerList) { + String[] providers = providerList.split(","); + + RuntimeException error = null; + + for (String provider : providers) { + try { + return (AnsiConsoleSupport) + Class.forName("org.fusesource.jansi.internal." + provider + ".AnsiConsoleSupportImpl") + .getConstructor() + .newInstance(); + } catch (Throwable t) { + if (error == null) { + error = new RuntimeException("Unable to create AnsiConsoleSupport provider"); + } + + error.addSuppressed(t); + } + } + + // User does not specify any provider, falling back to the default + if (error == null) { + return getDefaultProvider(); + } + + throw error; + } + + static { + String providerList = System.getProperty(JANSI_PROVIDERS); + + AnsiConsoleSupport ansiConsoleSupport = null; + Throwable err = null; + + try { + if (providerList == null) { + ansiConsoleSupport = getDefaultProvider(); + } else { + ansiConsoleSupport = findProvider(providerList); + } + } catch (Throwable e) { + err = e; + } + + String providerName = null; + AnsiConsoleSupport.CLibrary clib = null; + AnsiConsoleSupport.Kernel32 kernel32 = null; + + if (ansiConsoleSupport != null) { + try { + providerName = ansiConsoleSupport.getProviderName(); + clib = ansiConsoleSupport.getCLibrary(); + kernel32 = OSInfo.isWindows() ? ansiConsoleSupport.getKernel32() : null; + } catch (Throwable e) { + err = e; + } + } + + PROVIDER_NAME = providerName; + CLIBRARY = clib; + KERNEL32 = kernel32; + ERR = err; + } + + public static String getProviderName() { + return PROVIDER_NAME; + } + + public static AnsiConsoleSupport.CLibrary getCLibrary() { + if (CLIBRARY == null) { + throw new RuntimeException("Unable to get the instance of CLibrary", ERR); + } + + return CLIBRARY; + } + + public static AnsiConsoleSupport.Kernel32 getKernel32() { + if (KERNEL32 == null) { + if (OSInfo.isWindows()) { + throw new RuntimeException("Unable to get the instance of Kernel32", ERR); + } else { + throw new UnsupportedOperationException("Not Windows"); + } + } + + return KERNEL32; + } +} diff --git a/src/main/java/org/fusesource/jansi/ffm/AnsiConsoleSupportFfm.java b/src/main/java/org/fusesource/jansi/internal/ffm/AnsiConsoleSupportImpl.java similarity index 82% rename from src/main/java/org/fusesource/jansi/ffm/AnsiConsoleSupportFfm.java rename to src/main/java/org/fusesource/jansi/internal/ffm/AnsiConsoleSupportImpl.java index 2033fad8..bc252b41 100644 --- a/src/main/java/org/fusesource/jansi/ffm/AnsiConsoleSupportFfm.java +++ b/src/main/java/org/fusesource/jansi/internal/ffm/AnsiConsoleSupportImpl.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.fusesource.jansi.ffm; +package org.fusesource.jansi.internal.ffm; import java.io.IOException; import java.io.OutputStream; @@ -21,13 +21,22 @@ import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; -import org.fusesource.jansi.AnsiConsoleSupport; +import org.fusesource.jansi.internal.AnsiConsoleSupport; import org.fusesource.jansi.internal.OSInfo; import org.fusesource.jansi.io.AnsiProcessor; -import static org.fusesource.jansi.ffm.Kernel32.*; +import static org.fusesource.jansi.internal.ffm.Kernel32.*; + +public final class AnsiConsoleSupportImpl implements AnsiConsoleSupport { + + public AnsiConsoleSupportImpl() {} + + public AnsiConsoleSupportImpl(boolean checkNativeAccess) { + if (checkNativeAccess && !AnsiConsoleSupportImpl.class.getModule().isNativeAccessEnabled()) { + throw new UnsupportedOperationException("Native access is not enabled for the current module"); + } + } -public class AnsiConsoleSupportFfm implements AnsiConsoleSupport { @Override public String getProviderName() { return "ffm"; @@ -88,7 +97,7 @@ public int getLastError() { @Override public String getErrorMessage(int errorCode) { - return org.fusesource.jansi.ffm.Kernel32.getErrorMessage(errorCode); + return org.fusesource.jansi.internal.ffm.Kernel32.getErrorMessage(errorCode); } @Override diff --git a/src/main/java/org/fusesource/jansi/ffm/Kernel32.java b/src/main/java/org/fusesource/jansi/internal/ffm/Kernel32.java similarity index 99% rename from src/main/java/org/fusesource/jansi/ffm/Kernel32.java rename to src/main/java/org/fusesource/jansi/internal/ffm/Kernel32.java index 0cc409ac..a657e096 100644 --- a/src/main/java/org/fusesource/jansi/ffm/Kernel32.java +++ b/src/main/java/org/fusesource/jansi/internal/ffm/Kernel32.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.fusesource.jansi.ffm; +package org.fusesource.jansi.internal.ffm; import java.io.IOException; import java.lang.foreign.AddressLayout; @@ -309,7 +309,8 @@ public static String getErrorMessage(int errorCode) { int bufferSize = 160; try (Arena arena = Arena.ofConfined()) { MemorySegment data = arena.allocate(bufferSize); - FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, null, errorCode, 0, data, bufferSize, null); + FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM, MemorySegment.NULL, errorCode, 0, data, bufferSize, MemorySegment.NULL); return new String(data.toArray(JAVA_BYTE), StandardCharsets.UTF_16LE).trim(); } } diff --git a/src/main/java/org/fusesource/jansi/ffm/PosixCLibrary.java b/src/main/java/org/fusesource/jansi/internal/ffm/PosixCLibrary.java similarity index 97% rename from src/main/java/org/fusesource/jansi/ffm/PosixCLibrary.java rename to src/main/java/org/fusesource/jansi/internal/ffm/PosixCLibrary.java index bd4f1f73..30e959b9 100644 --- a/src/main/java/org/fusesource/jansi/ffm/PosixCLibrary.java +++ b/src/main/java/org/fusesource/jansi/internal/ffm/PosixCLibrary.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.fusesource.jansi.ffm; +package org.fusesource.jansi.internal.ffm; import java.lang.foreign.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.VarHandle; -import org.fusesource.jansi.AnsiConsoleSupport; +import org.fusesource.jansi.internal.AnsiConsoleSupport; final class PosixCLibrary implements AnsiConsoleSupport.CLibrary { private static final int TIOCGWINSZ; diff --git a/src/main/java/org/fusesource/jansi/ffm/WindowsAnsiProcessor.java b/src/main/java/org/fusesource/jansi/internal/ffm/WindowsAnsiProcessor.java similarity index 99% rename from src/main/java/org/fusesource/jansi/ffm/WindowsAnsiProcessor.java rename to src/main/java/org/fusesource/jansi/internal/ffm/WindowsAnsiProcessor.java index e933ff0a..cc6789e9 100644 --- a/src/main/java/org/fusesource/jansi/ffm/WindowsAnsiProcessor.java +++ b/src/main/java/org/fusesource/jansi/internal/ffm/WindowsAnsiProcessor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.fusesource.jansi.ffm; +package org.fusesource.jansi.internal.ffm; import java.io.IOException; import java.io.OutputStream; @@ -26,7 +26,7 @@ import org.fusesource.jansi.io.AnsiProcessor; import org.fusesource.jansi.io.Colors; -import static org.fusesource.jansi.ffm.Kernel32.*; +import static org.fusesource.jansi.internal.ffm.Kernel32.*; /** * A Windows ANSI escape processor, that uses JNA to access native platform diff --git a/src/main/java/org/fusesource/jansi/ffm/WindowsCLibrary.java b/src/main/java/org/fusesource/jansi/internal/ffm/WindowsCLibrary.java similarity index 97% rename from src/main/java/org/fusesource/jansi/ffm/WindowsCLibrary.java rename to src/main/java/org/fusesource/jansi/internal/ffm/WindowsCLibrary.java index c68854bf..2acfedb3 100644 --- a/src/main/java/org/fusesource/jansi/ffm/WindowsCLibrary.java +++ b/src/main/java/org/fusesource/jansi/internal/ffm/WindowsCLibrary.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.fusesource.jansi.ffm; +package org.fusesource.jansi.internal.ffm; import java.lang.foreign.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.VarHandle; import java.nio.charset.StandardCharsets; -import org.fusesource.jansi.AnsiConsoleSupport; +import org.fusesource.jansi.internal.AnsiConsoleSupport; import static java.lang.foreign.ValueLayout.*; diff --git a/src/main/java/org/fusesource/jansi/internal/AnsiConsoleSupportJni.java b/src/main/java/org/fusesource/jansi/internal/jni/AnsiConsoleSupportImpl.java similarity index 95% rename from src/main/java/org/fusesource/jansi/internal/AnsiConsoleSupportJni.java rename to src/main/java/org/fusesource/jansi/internal/jni/AnsiConsoleSupportImpl.java index 83f1a31e..ea3bc2fc 100644 --- a/src/main/java/org/fusesource/jansi/internal/AnsiConsoleSupportJni.java +++ b/src/main/java/org/fusesource/jansi/internal/jni/AnsiConsoleSupportImpl.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.fusesource.jansi.internal; +package org.fusesource.jansi.internal.jni; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; -import org.fusesource.jansi.AnsiConsoleSupport; +import org.fusesource.jansi.internal.AnsiConsoleSupport; import org.fusesource.jansi.io.AnsiProcessor; import org.fusesource.jansi.io.WindowsAnsiProcessor; @@ -33,7 +33,7 @@ import static org.fusesource.jansi.internal.Kernel32.STD_OUTPUT_HANDLE; import static org.fusesource.jansi.internal.Kernel32.SetConsoleMode; -public class AnsiConsoleSupportJni implements AnsiConsoleSupport { +public final class AnsiConsoleSupportImpl implements AnsiConsoleSupport { @Override public String getProviderName() { diff --git a/src/test/java/org/fusesource/jansi/AnsiTest.java b/src/test/java/org/fusesource/jansi/AnsiTest.java index 824c8d0d..332fb4b5 100644 --- a/src/test/java/org/fusesource/jansi/AnsiTest.java +++ b/src/test/java/org/fusesource/jansi/AnsiTest.java @@ -48,11 +48,7 @@ public void testClone() throws CloneNotSupportedException { @Test public void testApply() { - assertEquals( - "test", - Ansi.ansi() - .apply(ansi -> ansi.a("test")) - .toString()); + assertEquals("test", Ansi.ansi().apply(ansi -> ansi.a("test")).toString()); } @ParameterizedTest