GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Migrate many SciJava Common subsystems by ctrueden · Pull Request #295 · scijava/scijava

Migrate many SciJava Common subsystems #295

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.

Sign up for GitHub

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

Open
ctrueden wants to merge 59 commits into main
base: main
Loading
Loading
from scijava-common-migration

Uh oh!

There was an error while loading. Please reload this page.

Open

Migrate many SciJava Common subsystems #295

Changes from 1 commit
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
5e9836a
docs: add SciJava Common migration plan
ctrueden Sep 16, 2026
82cd7c8
scijava-ops-image: stop using SciJava Common utilities
ctrueden Sep 16, 2026
71852f7
scijava-discovery: make Discoverer yield lazy descriptors
ctrueden Sep 16, 2026
0771222
Pin scijava-discovery and scijava-priority versions
ctrueden Sep 16, 2026
4b8feff
Pin every consumed SciJava artifact version
ctrueden Sep 16, 2026
9084574
scijava-spi: add a module of framework-free interfaces
ctrueden Sep 16, 2026
9a5b15e
scijava-common3: absorb general utilities from SciJava Common
ctrueden Sep 16, 2026
24ba06b
scijava-common3: add FilePaths, from SciJava Common's FileUtils
ctrueden Sep 16, 2026
7d73482
scijava-index: port annotation indexing, with a lazy Discoverer
ctrueden Sep 16, 2026
313843f
docs: correct the Phase 2 injection design
ctrueden Sep 16, 2026
790ec74
scijava-io3: port locations, standalone on plain ServiceLoader
ctrueden Sep 16, 2026
c7842b0
scijava-io3: port data handles
ctrueden Sep 16, 2026
ea1ac3c
Make cancellation thread interruption
ctrueden Sep 16, 2026
ef68624
scijava-concurrent: let cancellation survive a parallel section
ctrueden Sep 16, 2026
df12101
docs: do not port io.nio
ctrueden Sep 16, 2026
80f4333
scijava-io3: port ByteBufferByteBank, keeping direct banks off-heap
ctrueden Sep 16, 2026
5039837
scijava-events: a typed event bus, written clean
ctrueden Sep 16, 2026
e09d280
docs: record incarnational versioning, and defer scijava-persist
ctrueden Sep 17, 2026
f65c690
scijava-io3: start at 3.0.0-SNAPSHOT
ctrueden Sep 17, 2026
caa9640
scijava-context: the container core
ctrueden Sep 17, 2026
7822074
scijava-context: the plugin tier
ctrueden Sep 17, 2026
394b956
scijava-context: declarative dependency injection
ctrueden Sep 17, 2026
de2e89f
scijava-context: @EventHandler scanning
ctrueden Sep 17, 2026
2e01213
docs: scrutinize the core services, and settle the preferences design
ctrueden Sep 17, 2026
9156ef6
docs: use TOML rather than YAML for the settings store
ctrueden Sep 17, 2026
634d940
scijava-execute: run things that declare their inputs and outputs
ctrueden Sep 17, 2026
1e1e662
scijava-execute: the pre/postprocessor chain
ctrueden Sep 17, 2026
95b4e57
docs: say how Fiji gets built on SciJava3
ctrueden Sep 17, 2026
0138735
scijava-execute: generalize beyond Java classes, for scripts
ctrueden Sep 17, 2026
0e4ac38
scijava-command: discoverable commands, and the metadata menus need
ctrueden Sep 17, 2026
80bab69
One opens, to the container
ctrueden Sep 17, 2026
cfead6d
Add the menu tree, and make Access a static utility
ctrueden Sep 17, 2026
b6ff554
scijava-harvest: prototype the parameter dialog model
ctrueden Sep 17, 2026
df48f01
Support computed parameter choices, closing the DynamicCommand gap
ctrueden Sep 17, 2026
58fd161
scijava-ui3 and scijava-swing: widgets, and dialogs to click on
ctrueden Sep 17, 2026
0a57e16
Fix the widget bugs the demos turned up, and fill in the gaps
ctrueden Sep 17, 2026
1384996
Ignore Eclipse's .factorypath
ctrueden Sep 17, 2026
46a5f3e
migration.md: the command row was ported two commits ago
ctrueden Sep 17, 2026
193a373
A Swing shell: menus, search, status -- the first whole-stack demo
ctrueden Sep 18, 2026
19e9d00
scijava-javafx: a second binding, and what it proved
ctrueden Sep 18, 2026
ad4ed60
scijava-awt: a third binding, and where the shared code belongs
ctrueden Sep 18, 2026
ada9894
scijava-convert3: one question, and it sits below the container
ctrueden Sep 18, 2026
22c0dc6
scijava-script3: a script is an Executable like any other
ctrueden Sep 18, 2026
40d687d
Scripts declare their presentation, and menus take more than one source
ctrueden Sep 18, 2026
7c100d4
Name the general thing what users call it: CommandInfo
ctrueden Sep 18, 2026
477a544
The legacy bridge, and the split package it found
ctrueden Sep 18, 2026
1f0084d
One name per foreign library: scijava-compat, -ops-compat, -imagej-co…
ctrueden Sep 18, 2026
1d8efbd
migration.md: say what the presentation metadata actually does
ctrueden Sep 18, 2026
3a2aa7e
scijava-cli: run the same commands without a display
ctrueden Sep 18, 2026
eccf33e
scijava-settings: one TOML file a person can read and edit
ctrueden Sep 19, 2026
7f4e5fa
Shorten submodule folder names
ctrueden Sep 20, 2026
680d702
Make Context.doDispose() less deadlock-prone
ctrueden Sep 25, 2026
a641987
Migrate incarnational versioning writeup to here
ctrueden Oct 26, 2026
e625d35
README: fix build badge
ctrueden Oct 26, 2026
ee70467
Fix wrong suggested scijava-ops-indexer version
ctrueden Oct 26, 2026
a3f8e24
Pin the migration plan's scijava-persist version
ctrueden Oct 26, 2026
289a91c
Fix javadoc errors
ctrueden Oct 26, 2026
e197fad
Make DiscoveryLazinessTest independent of test order
ctrueden Oct 28, 2026
ba91881
Address Copilot review feedback
ctrueden Oct 28, 2026
File filter
Filter by extension

Clear filters
Conversations
Failed to load comments.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Jump to
Jump to file
Failed to load files.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Diff view
Diff view
Prev Previous commit
Next Next commit
docs: correct the Phase 2 injection design
Constructor injection was the wrong call, for the reasons raised in
review: it publishes every dependency in a signature, so changing an
internal dependency becomes an API change and a binary compatibility
break unless old constructors are kept forever. It also cannot serve the
ServiceLoader tier, which requires a public no-arg constructor or a
static provider() factory.

The premise behind it was also wrong. I had assumed that letting the
container instantiate a plugin means exporting its package, which would
indeed break encapsulation -- callers could cast to implementations and
call non-API methods. But `opens` is not `exports`: a qualified
`opens <pkg> to <container>` grants deep reflective access at runtime
while leaving the package invisible at compile time.

Measured with a three-module test project on JDK 21, rather than assumed:

- ServiceLoader finds a provider declared with `provides` in a package
  that is neither exported nor opened.
- Reflective newInstance on such a package fails with
  IllegalAccessException; with a qualified `opens` it succeeds.
- setAccessible injection into a private field works with that opens.
- An ordinary module importing a class from the opened but unexported
  package fails to compile: "package ... is declared in module ..., which
  does not export it".
- A service with a private constructor and a public static provider()
  factory works; ServiceLoader prefers the factory.

So declarative field injection survives JPMS intact, encapsulation is
preserved, and the cost to a plugin module is one `opens` line. The claim
that we must give up private-field injection is removed.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013gJpzGd3ksbME4CN6Tu2ni
  • Loading branch information

Uh oh!

There was an error while loading. Please reload this page.

@ctrueden @claude
ctrueden and claude committed Oct 28, 2026
commit 313843fd7b1c8dcb8deb9674bf19f698ad8aae4a

There are no files selected for viewing

60 changes: 43 additions & 17 deletions 60 docs/migration-plan.md
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ The deliverable is not a reimplementation of SJC. It is:
- **Classpath-first, module-path-clean.** Fiji runs on the classpath, so JPMS
strictness binds only those who deliberately modularize. Design so that
modularization is *possible* without contorting for it: no split packages,
plugin classes public in exported packages, constructor injection rather
than private-field reflection, resources under `META-INF/`. The one thing we
give up is `setAccessible`-based injection into private fields.
resources under `META-INF/`, and a qualified `opens` to the container where
a module wants its implementations injected. Notably we give up **nothing**
here: implementation packages stay unexported and encapsulated, and
declarative field injection keeps working. See the JPMS section below for
the measurements behind this.
- **Leave SJC alone.** No functional changes, no Java version bump, no
delegation layer. Only `@deprecated` javadoc.
- **One-way migration.** Downstream components migrate by swapping the
Expand Down Expand Up @@ -188,17 +190,32 @@ elsewhere too).

### JPMS specifics

- Index resources are safe: `META-INF/json/...` is not a valid package name,
so it stays readable via `ClassLoader.getResources` from named modules.
- Instantiating a public class with a public no-arg constructor in an
**exported** package needs no `opens`.
- `setAccessible` on private fields — how SJC injects `@Parameter`
(`Context.java:510`, `ServiceHelper.java:317`) — **does** require `opens`.
This is the one thing forcing every plugin module to open itself.
These were measured, not reasoned about, with a three-module test project
(`javac --module-source-path`, run on the module path, JDK 21):

Therefore the SJ3 context uses **constructor injection** (or an explicit
`initialize(Context)`), so that `exports` suffices and `@Plugin` coexists with
JPMS.
| Situation | Result |
| --- | --- |
| Index resources (`META-INF/json/...`) read via `ClassLoader.getResources` | **works** — `META-INF` is not a valid package name, so it is never encapsulated |
| `ServiceLoader` finding a provider declared with `provides`, in a package that is neither exported nor opened | **works** — the `provides` declaration itself grants the access |
| Reflective `newInstance` on a public class in a package that is neither exported nor opened | **fails**: `IllegalAccessException` |
| The same, with a qualified `opens <pkg> to <container>` | **works** |
| `setAccessible` injection into a **private** field, with that qualified `opens` | **works** |
| An ordinary module importing a class from that opened-but-unexported package | **compile error**: "package ... is declared in module ..., which does not export it" |
| A service with a **private** constructor plus a `public static provider()` factory | **works** — `ServiceLoader` prefers the factory |

The load-bearing conclusion is the second-to-last row: **`opens` is not
`exports`.** A qualified `opens` grants the container deep reflective access at
runtime while leaving the package invisible to ordinary callers at compile
time. Implementations therefore stay encapsulated — callers cannot import
them, cannot cast to them, and cannot call their non-API methods — and the
container can still construct and inject them.

So the SJ3 context keeps **declarative field injection**, as SciJava Common
has always had. The cost to a plugin module is one line:

```java
opens my.plugins.impl to org.scijava.context;
```

### Logging

Expand Down Expand Up @@ -296,10 +313,19 @@ No application context required by anything in this phase.
### Phase 2 — The application context

- **`scijava-context`** (new, `org.scijava.context`): services with a
lifecycle and **constructor-based** dependency injection; plugin metadata
from `scijava-index`; instantiation via `Discoverer`; ordering via
`scijava-priority`. Plus the core services: object registry, prefs, thread,
app/status.
lifecycle and dependency injection; plugin metadata from `scijava-index`;
instantiation via `Discoverer`; ordering via `scijava-priority`. Plus the
core services: object registry, prefs, thread, app/status.
- **Dependencies are not constructor arguments.** Constructor injection
would publish every dependency in a signature, so changing an internal
dependency would be an API change and a binary compatibility break unless
old constructors were kept forever. It also cannot work for the
`ServiceLoader` tier, which requires a public no-arg constructor or a
static `provider()` factory. Dependencies are instead acquired after
construction — declaratively, by annotated field, as SciJava Common does —
which keeps them an implementation detail.
- Services may therefore have a **private** constructor and a
`public static provider()` factory, exposing no construction API at all.
- Deliberately a much smaller surface than SJC's `Context` +
`PluginService` + `PluginInfo` + the handler/wrapper/typed-service
hierarchy. Keep what is used; drop the rest.
Expand Down
Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.