Skip to content

Commit

Permalink
Refactoring AnsiConsoleSupport (#266)
Browse files Browse the repository at this point in the history
  • Loading branch information
Glavo committed Oct 2, 2023
1 parent ffd1688 commit a8961cb
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 110 deletions.
24 changes: 15 additions & 9 deletions src/main/java/org/fusesource/jansi/AnsiConsole.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
}
}
60 changes: 0 additions & 60 deletions src/main/java/org/fusesource/jansi/AnsiConsoleSupportHolder.java

This file was deleted.

18 changes: 9 additions & 9 deletions src/main/java/org/fusesource/jansi/AnsiMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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);
Expand All @@ -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."
Expand Down
10 changes: 4 additions & 6 deletions src/main/java/org/fusesource/jansi/WindowsSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -56,8 +56,4 @@ interface Kernel32 {
CLibrary getCLibrary();

Kernel32 getKernel32();

static AnsiConsoleSupport getInstance() {
return AnsiConsoleSupportHolder.get();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,30 @@
* 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;
import java.lang.foreign.Arena;
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()) {

This comment has been minimized.

Copy link
@gnodet

gnodet Oct 4, 2023

Member

@Glavo I actually have a problem with this line. On the standard macos terminal, it causes the FFM provider to not be selected by default when using the class path and not the module path (I do run java -jar target/jansi-xxx.jar). I wonder if this would need an additional check for modules or something similar ... ?

This comment has been minimized.

Copy link
@Glavo

Glavo Oct 4, 2023

Author Contributor

@Glavo I actually have a problem with this line. On the standard macos terminal, it causes the FFM provider to not be selected by default when using the class path and not the module path (I do run java -jar target/jansi-xxx.jar). I wonder if this would need an additional check for modules or something similar ... ?

I did not reproduce this issue. Have you added the --enable-native-access=ALL-UNNAMED option?

image

This comment has been minimized.

Copy link
@gnodet

gnodet Oct 4, 2023

Member

My point is that --enable-native-access=ALL-UNNAMED seems to only be necessary to avoid the warning that is otherwise printed. Do we really want to disable FFM support if the flag is not set ?

This comment has been minimized.

Copy link
@Glavo

Glavo Oct 4, 2023

Author Contributor

My point is that --enable-native-access=ALL-UNNAMED seems to only be necessary to avoid the warning that is otherwise printed. Do we really want to disable FFM support if the flag is not set ?

FFM is not disabled, it just won't be selected by default, users can select it by overriding the jansi.providers property.

I think this change should be made more imperceptible by default, printing warnings can be confusing to users.

This comment has been minimized.

Copy link
@Glavo

Glavo Oct 4, 2023

Author Contributor

My point is that --enable-native-access=ALL-UNNAMED seems to only be necessary to avoid the warning that is otherwise printed. Do we really want to disable FFM support if the flag is not set ?

Additionally, this warning from the JDK is only temporary. In the future, the JDK may throw exceptions.

throw new UnsupportedOperationException("Native access is not enabled for the current module");
}
}

public class AnsiConsoleSupportFfm implements AnsiConsoleSupport {
@Override
public String getProviderName() {
return "ffm";
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
}
Expand Down
Loading

0 comments on commit a8961cb

Please sign in to comment.