Files

220 lines
10 KiB
Java

package qualification;
import com.oracle.javafx.scenebuilder.kit.fxom.*;
import de.neitzel.fxdesigner.kit.*;
import java.lang.instrument.Instrumentation;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import javafx.application.Platform;
import javafx.scene.control.*;
import javafx.stage.*;
/** External qualification agent; never included in the distributable application. */
public final class PackageProbe {
static <T> T fx(Callable<T> action) throws Exception {
var result = new CompletableFuture<T>();
Platform.runLater(
() -> {
try {
result.complete(action.call());
} catch (Throwable e) {
result.completeExceptionally(e);
}
});
return result.get(20, TimeUnit.SECONDS);
}
static void await(Callable<Boolean> condition) throws Exception {
long until = System.nanoTime() + TimeUnit.SECONDS.toNanos(100);
while (System.nanoTime() < until) {
if (condition.call()) return;
Thread.sleep(100);
}
throw new AssertionError("Packaged workflow condition timed out");
}
public static void premain(String ignored, Instrumentation instrumentation) {
Thread.ofPlatform()
.daemon()
.start(
() -> {
Path evidence = Path.of(System.getProperty("package.evidence"));
try {
// Main launches JavaFX normally; the probe only observes an existing application.
for (int i = 0; i < 200; i++) {
try {
fx(() -> null);
break;
} catch (IllegalStateException e) {
Thread.sleep(100);
}
}
await(
() ->
fx(
() ->
Window.getWindows().stream()
.anyMatch(w -> w instanceof Stage s && s.getScene() != null)));
String home = System.getProperty("java.home");
if (!home.contains("NeitzelFXDesigner.app/Contents/runtime"))
throw new AssertionError("External host JVM: " + home);
if (!System.getProperty("javafx.runtime.version").startsWith("26"))
throw new AssertionError("Wrong JavaFX");
String patch =
Class.forName(
"com.oracle.javafx.scenebuilder.kit.editor.panel.inspector.editors.I18nStringEditor")
.getProtectionDomain()
.getCodeSource()
.getLocation()
.toString();
try {
Class.forName("de.neitzel.fxdesigner.preview.PreviewSession");
throw new AssertionError("Target API leaked into the host classpath");
} catch (ClassNotFoundException expected) {
}
if (!patch.contains("designer-kit-ui-"))
throw new AssertionError("Unpatched Kit won classpath order: " + patch);
if (Boolean.getBoolean("package.sourceOnly")) {
await(
() ->
fx(
() ->
Window.getWindows().stream()
.anyMatch(
w ->
w instanceof Stage s
&& s.getScene().getRoot()
instanceof SourceOnlyPane)));
var source =
fx(
() ->
(SourceOnlyPane)
Window.getWindows().stream()
.filter(
w ->
w instanceof Stage st
&& st.getScene().getRoot()
instanceof SourceOnlyPane)
.map(w -> ((Stage) w).getScene().getRoot())
.findFirst()
.orElseThrow());
await(() -> fx(() -> ((TextArea) source.getCenter()).isEditable()));
String original =
Files.readString(Path.of(System.getProperty("package.document")));
fx(
() -> {
((TextArea) source.getCenter())
.appendText("<!-- packaged fallback edit -->");
return null;
});
fx(source::save).toCompletableFuture().get(10, TimeUnit.SECONDS);
if (!Files.readString(Path.of(System.getProperty("package.document")))
.equals(original + "<!-- packaged fallback edit -->"))
throw new AssertionError("Fallback did not save exact source");
} else {
var pane =
fx(
() ->
(DesignerPane)
Window.getWindows().stream()
.filter(
w ->
w instanceof Stage s
&& s.getScene().getRoot() instanceof DesignerPane)
.map(w -> ((Stage) w).getScene().getRoot())
.findFirst()
.orElseThrow());
if (!fx(
() -> ((Stage) pane.getScene().getWindow()).getTitle().contains("[fixture]")))
throw new AssertionError("Active application context is not displayed");
await(() -> fx(() -> pane.kit().editor().getFxomDocument() != null));
fx(
() -> {
pane.lookupAll(".button").stream()
.filter(
n ->
n instanceof Button b
&& b.getText().equals("Build and Refresh"))
.map(n -> (Button) n)
.findFirst()
.orElseThrow()
.fire();
return null;
});
if (Boolean.getBoolean("package.missingRuntime")) {
var member = DesignerPane.class.getDeclaredField("buildRun");
member.setAccessible(true);
var run =
fx(() -> (de.neitzel.fxdesigner.build.BuildRefresh.Run) member.get(pane));
var result = run.completion().toCompletableFuture().get(30, TimeUnit.SECONDS);
if (result.status() != de.neitzel.fxdesigner.build.BuildRefresh.Status.FAILED
|| !result.message().contains("Invalid JDK installation"))
throw new AssertionError("Missing runtime not diagnosed: " + result);
var source = fx(() -> (TextArea) pane.lookup("#fxml-source"));
await(() -> fx(source::isEditable));
if (source == null) throw new AssertionError("Source editing unavailable");
} else {
await(
() ->
fx(
() -> {
var object =
new FXOMFxIdIndex(pane.kit().editor().getFxomDocument())
.lookup("card");
return object instanceof FXOMInstance i
&& i.getSceneGraphObject() instanceof SnapshotRegion r
&& r.image() != null
&& r.statusText().equals("Target runtime");
}));
try (var doc =
pane.kit()
.host()
.documents()
.open(Path.of(System.getProperty("package.document")).toUri())
.toCompletableFuture()
.get()) {
ProjectionHostChecks.run(pane, doc);
}
}
}
Files.writeString(
evidence.resolve("runtime.txt"),
home + "\n" + patch + "\n" + System.getProperty("javafx.runtime.version"));
Files.writeString(
evidence.resolve("workers.txt"),
String.join(
"\n",
ProcessHandle.current()
.descendants()
.map(p -> Long.toString(p.pid()))
.toList()));
fx(
() -> {
for (var window : List.copyOf(Window.getWindows()))
if (window instanceof Stage stage) {
// All checks save their drafts; exercise real onHidden cleanup without a
// confirmation dialog.
stage.setOnCloseRequest(null);
stage.close();
}
return null;
});
Files.writeString(
evidence.resolve("SUCCESS.txt"),
"PASS packaged launcher, bundled runtime/Kit, workflow and window cleanup\n");
Platform.exit();
} catch (Throwable failure) {
try (var out =
new java.io.PrintWriter(
Files.newBufferedWriter(evidence.resolve("FAILURE.txt")))) {
failure.printStackTrace(out);
} catch (Exception ignoredError) {
}
failure.printStackTrace();
System.exit(1);
}
});
}
}