Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite injection of CTCGraphicsEnvironment #16

Merged
merged 19 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ target/

# OS X
.DS_Store

# jenv


# jenv
.java-version

# log files
*.log
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
[![License](https://img.shields.io/github/license/CaciocavalloSilano/caciocavallo.svg)](https://raw.githubusercontent.com/CaciocavalloSilano/caciocavallo/master/LICENSE)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.caciocavallosilano/cacio-tta/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.caciocavallosilano/cacio-tta)

## Please note
<span style="color:blue">This is a fork of the Caciocavallo project. It's purpose is to enable using a fixed version in the OHDSI Rabbit tools (for Java 18+) until this fix is available in the
parent project. An issue for this has been created in the parent project, with an offer to create a PR. (Jan Blom)</span>

## Introduction

One problem with running GUI tests is that they need to create windows, grab keyboard focus, and do all sorts of interaction with the screen.
Expand Down
4 changes: 2 additions & 2 deletions cacio-shared/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<source>${cacio.java.version}</source>
<target>${cacio.java.version}</target>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to define this once, I'm a bit of a Maven newbie, doesn't this need to be added to a properties section somewhere?

<compilerArgs>
<arg>-XDignore.symbol.file=true</arg>
<arg>--add-exports=java.desktop/java.awt.peer=ALL-UNNAMED</arg>
Expand Down
15 changes: 13 additions & 2 deletions cacio-tta/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@
<artifactId>jide-oss</artifactId>
<version>3.6.18</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.11</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.14.11</version>
</dependency>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if these should have a provided scope or perhaps shadow the jar. In my case I'll want to use whichever version the Spring Boot bom defines. I'll leave this for now but might consider that change later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am less familiar with Spring Boot and boms. I generally go for the simplest thing that works and makes sense to me.

</dependencies>

<build>
Expand All @@ -54,8 +65,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<source>${cacio.java.version}</source>
<target>${cacio.java.version}</target>
<compilerArgs>
<arg>-XDignore.symbol.file=true</arg>
<arg>--add-exports=java.desktop/java.awt.peer=ALL-UNNAMED</arg>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,14 @@

public class CTCGraphicsEnvironment extends SunGraphicsEnvironment {

private static final CTCGraphicsEnvironment INSTANCE = new CTCGraphicsEnvironment();
public CTCGraphicsEnvironment() {
SurfaceManagerFactory.setInstance(new CTCSurfaceManagerFactory());
}

public static CTCGraphicsEnvironment getInstance() {
return INSTANCE;
}
@Override
protected int getNumScreens() {
return 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import sun.awt.image.VolatileSurfaceManager;
import sun.java2d.SurfaceManagerFactory;

class CTCSurfaceManagerFactory extends SurfaceManagerFactory {
public class CTCSurfaceManagerFactory extends SurfaceManagerFactory {

@Override
public VolatileSurfaceManager createVolatileManager(SunVolatileImage image,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import sun.awt.image.VolatileSurfaceManager;
import sun.java2d.SurfaceData;

class CTCVolatileSurfaceManager extends VolatileSurfaceManager {
public class CTCVolatileSurfaceManager extends VolatileSurfaceManager {

protected CTCVolatileSurfaceManager(SunVolatileImage vImg, Object context) {
super(vImg, context);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.github.caciocavallosilano.cacio.ctc.junit;

import com.github.caciocavallosilano.cacio.ctc.CTCGraphicsEnvironment;
import net.bytebuddy.implementation.bind.annotation.*;

import java.awt.*;


public class CTCInterceptor {
@RuntimeType
public static GraphicsEnvironment intercept() {
return CTCGraphicsEnvironment.getInstance();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,46 @@
*/
package com.github.caciocavallosilano.cacio.ctc.junit;

import com.github.caciocavallosilano.cacio.ctc.CTCGraphicsEnvironment;
import com.github.caciocavallosilano.cacio.ctc.CTCToolkit;
import com.github.caciocavallosilano.cacio.ctc.*;
import com.github.caciocavallosilano.cacio.peer.PlatformWindowFactory;
import com.github.caciocavallosilano.cacio.peer.managed.FullScreenWindowFactory;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.*;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.platform.commons.util.AnnotationUtils;

import javax.swing.plaf.metal.MetalLookAndFeel;
import java.awt.*;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Method;
import java.util.Map;


import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;


public class CacioExtension implements ExecutionCondition {
// https://stackoverflow.com/a/56043252/1050369
private static final VarHandle MODIFIERS;

static {
try {
ByteBuddyAgent.install();

var lookup = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
MODIFIERS = lookup.findVarHandle(Field.class, "modifiers", int.class);
} catch (IllegalAccessException | NoSuchFieldException ex) {
Expand All @@ -54,42 +73,87 @@ public class CacioExtension implements ExecutionCondition {

static {
try {
injectCTCGraphicsEnvironment();

Field toolkit = Toolkit.class.getDeclaredField("toolkit");
toolkit.setAccessible(true);
toolkit.set(null, new CTCToolkit());

Field defaultHeadlessField = java.awt.GraphicsEnvironment.class.getDeclaredField("defaultHeadless");
defaultHeadlessField.setAccessible(true);
defaultHeadlessField.set(null, Boolean.TRUE);
defaultHeadlessField.set(null, Boolean.FALSE);
Field headlessField = java.awt.GraphicsEnvironment.class.getDeclaredField("headless");
headlessField.setAccessible(true);
headlessField.set(null, Boolean.TRUE);

Class<?> geCls = Class.forName("java.awt.GraphicsEnvironment$LocalGE");
Field ge = geCls.getDeclaredField("INSTANCE");
ge.setAccessible(true);
defaultHeadlessField.set(null, Boolean.FALSE);
headlessField.set(null, Boolean.FALSE);

makeNonFinal(ge);

Class<?> smfCls = Class.forName("sun.java2d.SurfaceManagerFactory");
Field smf = smfCls.getDeclaredField("instance");
smf.setAccessible(true);
smf.set(null, null);

ge.set(null, new CTCGraphicsEnvironment());
} catch (Exception e) {
e.printStackTrace();
}

System.setProperty("swing.defaultlaf", MetalLookAndFeel.class.getName());
}

public static void makeNonFinal(Field field) {
int mods = field.getModifiers();
if (Modifier.isFinal(mods)) {
MODIFIERS.set(field, mods & ~Modifier.FINAL);
public static void injectCTCGraphicsEnvironment() throws ClassNotFoundException, IOException {
/*
* ByteBuddy is used to intercept the methods that return the graphics environment in use
* (java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment() and
* sun.awt.PlatformGraphicsInfo.createGE())
*
* Since java.awt.GraphicsEnvironment is loaded by the bootstrap class loader,
* all classes used by CTCGraphicsEnvironment also need to be available to the bootstrap class loader,
* as that class loader also loads the CTCInterceptor class, which will instantiate CTCGraphicsEnvironment.
*/
injectClassIntoBootstrapClassLoader(
CTCInterceptor.class,
CTCGraphicsEnvironment.class,
CTCSurfaceManagerFactory.class,
CTCGraphicsConfiguration.class,
PlatformWindowFactory.class,
FullScreenWindowFactory.class,
CTCGraphicsDevice.class,
CTCVolatileSurfaceManager.class);

ByteBuddy byteBuddy = new ByteBuddy();

byteBuddy
.redefine(
TypePool.Default.ofSystemLoader().describe("java.awt.GraphicsEnvironment").resolve(),
ClassFileLocator.ForClassLoader.ofSystemLoader())
.method(ElementMatchers.named("getLocalGraphicsEnvironment"))
.intercept(
MethodDelegation.to(CTCInterceptor.class))
.make()
.load(
Object.class.getClassLoader(),
ClassReloadingStrategy.fromInstalledAgent());

TypeDescription platformGraphicInfosType;
platformGraphicInfosType = TypePool.Default.ofSystemLoader().describe("sun.awt.PlatformGraphicsInfo").resolve();
ClassFileLocator locator = ClassFileLocator.ForClassLoader.ofSystemLoader();

byteBuddy
.redefine(
platformGraphicInfosType,
locator)
.method(
nameStartsWith("createGE"))
.intercept(
MethodDelegation.to(GraphicsEnvironmentInterceptor.class))
.make()
.load(
Thread.currentThread().getContextClassLoader(),
ClassReloadingStrategy.fromInstalledAgent());

}

public static class GraphicsEnvironmentInterceptor {
@RuntimeType
public static Object intercept(@Origin Method method, @AllArguments final Object[] args) throws Exception {
return CTCGraphicsEnvironment.getInstance();
}
}

Expand All @@ -100,4 +164,12 @@ public final ConditionEvaluationResult evaluateExecutionCondition(ExtensionConte
.map(annotation -> ConditionEvaluationResult.enabled("@GUITest is present"))
.orElse(ConditionEvaluationResult.enabled("@GUITest is not present"));
}

private static void injectClassIntoBootstrapClassLoader(Class... classes) throws IOException {
for (Class<?> clazz: classes) {
final byte[] buffer = clazz.getClassLoader().getResourceAsStream(clazz.getName().replace('.', '/').concat(".class")).readAllBytes();
ClassInjector.UsingUnsafe injector = new ClassInjector.UsingUnsafe(null);
injector.injectRaw(Map.of(clazz.getName(), buffer));
}
}
}
9 changes: 7 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
<module>cacio-tta</module>
</modules>

<properties>
<cacio.java.version>17</cacio.java.version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disregard my other comment, I see it now

</properties>

<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
Expand Down Expand Up @@ -112,8 +116,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<source>${cacio.java.version}</source>
<target>${cacio.java.version}</target>
<compilerArgs>
<arg>-XDignore.symbol.file=true</arg>
</compilerArgs>
Expand All @@ -130,6 +134,7 @@
<java.awt.headless>false</java.awt.headless>
</systemPropertyVariables>
<argLine>
-XX:+EnableDynamicAgentLoading
--add-exports=java.desktop/java.awt=ALL-UNNAMED
--add-exports=java.desktop/java.awt.peer=ALL-UNNAMED
--add-exports=java.desktop/sun.awt.image=ALL-UNNAMED
Expand Down
Loading