Building dynamic Java Module System layers to integrate JavaFX 11 in Equinox

One of the most fundamental features of the e(fx)clipse runtime is to integrate JavaFX into the Equinox OSGi-Container and even a running Eclipse IDE.

We currently support the following setups:

  • JavaFX 8
  • JavaFX 9/10
  • JavaFX 11

and the integration for all those versions is a bit different. I don’t want to go into details but starting with JavaFX-11 we need to spin up a new Java-Module-System-Layer at runtime because we can not assume JavaFX being part of the JRE running your OSGi-Container (Eclipse IDE).

Since JavaFX-9 we spin up a dynamic layer to implement JavaFX-SWT-Integration and we adapted that logic for JavaFX-11 to load all JavaFX-11 modules.

The code we have works like this and it works prefectly fine until someone like ControlsFX comes along and does not play by the rules trying to load classes from unexported packages like com.sun.javafx.runtime.VersionInfo.

The standard answer from ControlsFX to fix that problem temporarily is to force the module-system to export them using –add-exports=javafx.base/com.sun.javafx.runtime=ALL-UNNAMED.

Unfortunately this workaround does not work in our case because the command-line flag only allows to modify modules of the Boot-Layer but not those created in dynamic ones like those we construct inside our JavaFX-OSGi integration.

I was investigating yesterday how one could fix this problem but could not come up with a good solution (one that does not call into internals of the module system) until I tweeted

about it and Tom Watson (one of the maintainers of Equinox) pointed me into the right direction.

So the solution is

and now I have to think how we expose that to in our OSGi-Integration.

Advertisements
Posted in Uncategorized | Leave a comment

Supporting OpenJFX 11 from JDK11 onwards in e(fx)clipse

So starting with JDK-11 OpenJFX is not part of any downloadable distribution. As JavaFX is designed to run on the module-path (and tested only there) you have 2 options to run JavaFX inside OSGi:
* You create your own JDK-Distribution using jlink
* You launch the VM you want to use JavaFX adding the JavaFX-Modules

While the 2nd solution is doable for RCP-Applications it is less than a nice one, and for integrating into external frameworks (like the Eclipse IDE) it is not possible at all. So we need a different solution to satisfy both usecases.

The solution to this problem is that e(fx)clipse installs a classloader hook using the Equinox AdapterHook-Framework (you can do crazy stuff with that) and on the fly spins up a new Java-Module-Layer containing all the JavaFX-Modules and uses the classloader from the Module-Layer to load the JavaFX-Classes.

With this strategy you can supply the JavaFX-Modules (including the native bits) required for your application to run as part of your p2-repository.

Posted in Uncategorized | 5 Comments

Fluent-Log API landed in e(fx)clipse

Last week I came a cross Googles FLogger-API and I really liked it.

Back in e(fx)clipse land I started to miss it but because introducing a dependency to other log-frameworks is not possible – I implemented our own fluent log API inspired by Flogger.

So how do you use it:


// if you have a logger
Logger logger = LoggerCreator.createLogger( Sample.class );
FluentLogger flogger = FluentLogger.of( logger );

// if you use @Log
@Inject
@Log
FluentLogger flogger;

// Log something
FluentLogContext debug = flogger.atDebug();
debug.log( "Hello World" );
debug.log( "Hello World with format %s", 10 );
debug.log( () -> "Lazy Hello World" );
debug.log( t -> "Lazy Hello World with Context" + t, o );

// Log with exception
try {
   // ...
} catch( Throwable l ) {
  flogger.atInfo().withException( t ).log( "Hello World" );
}

// Throttle: Only log every 100 log statement
flogger.atInfo().throttleByCount(100)
  .log( "Log every 100 time" );

// Throttle: Only log every minute
flogger.atInfo().throttleByTime(1, TimeUnit.MINUTES)
  .log( "Log every minute" );

// Build your own condition fluent addition
logger.atInfo().with( Throttle::new ).every( 100 )
  .log( "Log every 100 time" )
Posted in e(fx)clipse | 6 Comments

Proper OSGi access restriction warnings for e(fx)clipse maven projects

In the last few days I worked on the last required feature to abandon PDE and use maven (with the bnd-maven-plugin) and m2e.

Does anyone reading my blog know if IntelliJ and Netbeans also have a feature like this?

Posted in e(fx)clipse, Uncategorized | 4 Comments

Cross IDE Dev-Support for “e4 on JavaFX” – We are almost there

In the last few days I made a major step towards supporting all major Java-IDEs (Eclipse, Netbeans, IntelliJ IDEA) to develop “e4 on JavaFX” applications.

The key to this multi-IDE support is that we don’t use the MANIFEST-First approach from PDE but we use:

  • Maven 3.5
  • bnd-maven-plugin
  • A custom maven-plugin for launching in Netbeans and IntelliJ

Eclipse

For every member on your team who likes the Eclipse IDE.

In Eclipse we use a custom launcher who uses the maven-project dependency information to launch OSGi and install all necessary bundles.

Netbeans

For every member on your team who likes Netbeans

As you notice hot-code replacement is not yet working (or maybe I’m using Netbeans wrong?) Is there anything i need to do after having changed the file to hot-swap it?

IntelliJ IDEA

For every member on your team who likes IntelliJ

As you notice hot-code replacement is not yet working (or maybe I’m using IntelliJ IDEA wrong?) Is there anything i need to do after having changed the file to hot-swap it?

Posted in e(fx)clipse, Uncategorized | 10 Comments

Introducing (another/additional) JavaFX TestFramework

Let me start with the statement that TestFX is somewhat the default JUnit-Testframework for JavaFX application and is what we proposed to use to all our customers and our projects until today where we introduce our own one.

There are 2 different problem vectors we have:

  • A licensing issue we have with it at Eclipse.org: TestFX is licensed under EUPL which to me as a software developer looks ok but it looks like the IP-Department at Eclipse.org is not happy about it and refused me to use it. I don’t blame anyone but need to cope with the situation as is!
  • Implementing JUnit-Tests for OSGi-JavaFX applications: This is a pure technical issue and something we could have solved (although it would have meant to change the way TestFX works) but after having hit the licensing problem my motivation to resolve that problem was not really there

Anyways let’s look at what I’ve started to implement as a replacement for TestFX.

BestSolution FX-Test

First of all we decided for now that we don’t integrate the test-framework into e(fx)clipse but treat it as an independent entity at github and release it under EPL from there.

Writing JUnit-Tests is done by subclassing a base class (current only one available is FXComponentTest) and implementing your JUnit-Tests might look like this:

@Test
public void sample() {
  // Search with a css-selector query
  // and generated a click on the button
  rcontroller().cssFirst(".button").get().click();
}

In contrast to TestFX, tests written with our API can not run directly but you need to decided if you want to:

  • Use a specific runner using @RunWith(FXRunner.class)
  • Use a Rule @FXTest and annotate all UI-Test methods with it

which on the plus-side means that the @Test-methods are executed on the JavaFX-UI-Thread (unlike TestFX where they are executed on another thread most like the main-thread)

For more information that a look at the project README.md.

Let me close this post saying that we are very early in the development and things are still in the flux so we are happy for any feedback we get.

Posted in e(fx)clipse | 6 Comments

e(fx)clipse 3.0.0 is released

e(fx)clipse has been released on June 6th but because of the high load of JavaFX projects we are working on I did not have time to write the public release announcement.

In total we have worked on ~100 tickets most of them adding new features. We also moved our dev infrastructure to github.com and while doing that we’ve split the project into 2 repositories:

We believe that moving to github and the upcoming pure maven-build-story we currently work on in an extra branch will make it easier for others to consume our libraries in none OSGi/Eclipse/e4-Projects.

While we are switching our project structure to pure maven you can already consume our libraries from a maven-repository we host ourselves (see http://maven.bestsolution.at/)

Let’s take a short tour through some of the 3.0 highlights.

Support for Java 9

3.0 is the first release who is fully compatible with Java9 and JPMS. Historically we used non-public APIs and even used reflection to eg “hack” DnD in TabFolder. All code that would be broken in Java9 has been reworked to run on Java8 and Java9.

Some of the none public APIs we used in Java8 have been promoted to public API in Java9 and to support both Java 8 and 9 in the same codebase we extracted those into utility class org.eclipse.fx.ui.controls.JavaFXCompatUtil:

Java 8 Java 9 JavaFXCompatUtil
Window#impl_getWindows() Window#getWindows() getAllWindows()
KeyCode#impl_getChar() KeyCode#getChar() getChar(KeyCode)
KeyCode#impl_getCode() KeyCode#getCode() getCode(KeyCode)

Utilities to work with JavaFX Properties

FXBindings

JavaFX has a Bindings class to setup bindings between different Observables but we missed some features we frequently need in our application code hence we added an org.eclipse.fx.core.bindings.FXBindings who eg has:

  • tenaryBinding(ObservableBooleanValue, ObservableValue<T>, ObservableValue<T>) : Binding<T>
    allowing you to defined if-else in a JavaFX binding way
  • concat(ObservableList<? extends A>...) : ListBinding<A>
    to concat the lists to one and keep the target updated when one of the source lists change
  • concat(String, ObservableValue<T>...) : StringBinding
    concat the lists and concat the items with the given delimiter and keep the binding updated
  • bindContent(List<T>, ObservableList<E>, Function<E, T>) : Subscription
    similar to Bindings.bindContent but allows to use a converter function

A very special feature is a BindingStream you can create with bindStream(ObservableValue<T>) who is similar to Bindings.select but is:

  • Typesafe
  • Provide a Property as the leaf

Let’s look at a concrete example.

class Person {
  public ObjectProperty<Address> address();
}

class Address {
  public StringProperty street() { /* ... */ }
}

class UI {

  ObjectProperty<Person> currentPerson = /* ... */;

  TextField street;

  bindUI() {
     street.textProperty().bindBidirectional( 
      FXBindings.bindStream( currentPerson )
        .map( Person::address )
        .collect( 
           FXCollectors.toProperty( Address::street ) 
        )
     );
  }

}

@ContextValue improvements

@ContextValue is IMHO one of the coolest concepts we introduced in e(fx)clipse to make reuseable components. In 3.0 we added a scope-Property who allows you to fix a publishing scope.

As of 3.0 we support:

  • APPLICATION: Publish the value in the IEclipseContext of the application
  • LOCAL: Publish the value in the local IEclipseContext
  • DYNAMIC: Publish the value using IEclipsContext#modify and the application container is responsible to mark the target context

with DYNAMIC as the default.

ThreadSynchronize improvements

Halt program flow

We added a new API to halt the program flow (NOT the event loop) like this:


ThreadSynchronize t = ...;
TextField username = new TextField();

BlockCondition<String> w = new BlockCondition<>();
username.setOnAction( 
  e -> w.release( username.getText() ) );

System.out.println(t.block( w ));

Headless implementation

We at BestSolution use the MVVM-Pattern for our JavaFX applications and there we fetch data from backend-services in none-ui-threads and so we need to synchronize from a Background- to the UI-thread using ThreadSynchronize.

While things work perfectly fine in the real application because the JavaFX Framework is up and running things fall apart when you test your ViewModel in a headless JUnit-Tests. For that purpose there’s now the possibility to create a ThreadSynchronize instance like this:

ThreadSynchronize t = 
  ThreadSynchronize.createBasicThreadSyncronize(
    new EventLoop()
  );
Posted in e(fx)clipse, Uncategorized | 1 Comment