Content of file JavaFXSafeProxyCreator.java.html

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>JavaFXSafeProxyCreator.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">SteelBlue - JavaFX Bindings</a> &gt; <a href="index.source.html" class="el_package">it.tidalwave.ui.javafx</a> &gt; <span class="el_source">JavaFXSafeProxyCreator.java</span></div><h1>JavaFXSafeProxyCreator.java</h1><pre class="source lang-java linenums">/*
 * *********************************************************************************************************************
 *
 * SteelBlue: DCI User Interfaces
 * http://tidalwave.it/projects/steelblue
 *
 * Copyright (C) 2015 - 2021 by Tidalwave s.a.s. (http://tidalwave.it)
 *
 * *********************************************************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the &quot;License&quot;); 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 &quot;AS IS&quot; 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.
 *
 * *********************************************************************************************************************
 *
 * git clone https://bitbucket.org/tidalwave/steelblue-src
 * git clone https://github.com/tidalwave-it/steelblue-src
 *
 * *********************************************************************************************************************
 */
package it.tidalwave.ui.javafx;

import javax.annotation.Nonnull;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.io.IOException;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.application.Platform;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import it.tidalwave.role.ui.javafx.JavaFXBinder;
import it.tidalwave.role.ui.javafx.impl.util.JavaFXSafeProxy;
import it.tidalwave.role.ui.javafx.impl.DefaultJavaFXBinder;
import it.tidalwave.role.ui.javafx.impl.util.ReflectionUtils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import static lombok.AccessLevel.PRIVATE;

/***********************************************************************************************************************
 *
 * This facility class create a thread-safe proxy for the JavaFX delegate (controller). Thread-safe means that it can
 * be called by any thread and the JavaFX UI related stuff will be safely invoked in the JavaFX UI Thread.
 * It is usually used in this way:
 *
 * &lt;pre&gt;
 * // This is a Spring bean
 * public class JavaFxFooBarPresentation implements FooBarPresentation
 *   {
 *     private static final String FXML_URL = &quot;/my/package/javafx/FooBar.fxml&quot;;
 *
 *     @Inject
 *     private FlowController flowController;
 *
 *     private final NodeAndDelegate nad = createNodeAndDelegate(getClass(), FXML_URL);
 *
 *     private final FooBarPresentation delegate = nad.getDelegate();
 *
 *     public void showUp()
 *       {
 *         flowController.doSomething(nad.getNode());
 *       }
 *
 *     public void showData (final String data)
 *       {
 *         delegate.showData(data);
 *       }
 *   }
 * &lt;/pre&gt;
 *
 * The method {@link #createNodeAndDelegate(java.lang.Class, java.lang.String)} safely invokes the {@link FXMLLoader}
 * and returns a {@link NodeAndDelegate} that contains both the visual {@link Node} and its delegate (controller).
 *
 * The latter is wrapped by a safe proxy that makes sure that any method invocation (such as {@code showData()} in the
 * example is again executed in the JavaFX UI Thread. This means that the Presentation object methods can be invoked
 * in any thread.
 *
 * For method returning {@code void}, the method invocation is asynchronous; that is, the caller is not blocked waiting
 * for the method execution completion. If a return value is provided, the invocation is synchronous, and the caller
 * will correctly wait the completion of the execution in order to get the result value.
 *
 * A typical JavaFX delegate (controller) looks like:
 *
 * &lt;pre&gt;
 * // This is not a Spring bean - created by the FXMLLoader
 * public class JavaFxFooBarPresentationDelegate implements FooBarPresentation
 *   {
 *     @FXML
 *     private Label label;
 *
 *     @FXML
 *     private Button button;
 *
 *     @Inject // the only thing that can be injected, by means of JavaFXSafeProxyCreator
 *     private JavaFxBinder binder;
 *
 *     @Override
 *     public void bind (final UserAction action)
 *       {
 *         binder.bind(button, action);
 *       }
 *
 *     @Override
 *     public void showData (final String data)
 *       {
 *         label.setText(data);
 *       }
 *  }
 * &lt;/pre&gt;
 *
 * Not only all the methods invoked on the delegate are guaranteed to run in the JavaFX UI thread, but also its
 * constructor, as per JavaFX requirements.
 *
 * A Presentation Delegate must not try to have dependency injection from Spring (for instance, by means of AOP),
 * otherwise a deadlock could be triggered. Injection in constructors is safe.
 *
 * @author  Fabrizio Giudici
 *
 **********************************************************************************************************************/
<span class="nc" id="L131">@Slf4j</span>
public class JavaFXSafeProxyCreator
  {
<span class="nc" id="L134">    private static final String P_TIMEOUT = JavaFXSafeProxyCreator.class.getName() + &quot;.initTimeout&quot;;</span>
<span class="nc" id="L135">    private static final int initializerTimeout = Integer.getInteger(P_TIMEOUT, 10);</span>

<span class="nc" id="L137">    public static final Map&lt;Class&lt;?&gt;, Object&gt; BEANS = new HashMap&lt;&gt;();</span>

<span class="nc" id="L139">    @Getter</span>
<span class="nc" id="L140">    private static final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();</span>

<span class="nc" id="L142">    @Getter</span>
<span class="nc" id="L143">    private static final JavaFXBinder javaFxBinder = new DefaultJavaFXBinder(executor);</span>

    static
      {
<span class="nc" id="L147">        executor.setWaitForTasksToCompleteOnShutdown(false);</span>
<span class="nc" id="L148">        executor.setThreadNamePrefix(&quot;javafxBinder-&quot;);</span>
        // Fix for STB-26
<span class="nc" id="L150">        executor.setCorePoolSize(1);</span>
<span class="nc" id="L151">        executor.setMaxPoolSize(1);</span>
<span class="nc" id="L152">        executor.setQueueCapacity(10000);</span>
<span class="nc" id="L153">        BEANS.put(JavaFXBinder.class, javaFxBinder);</span>
<span class="nc" id="L154">        BEANS.put(Executor.class, executor);</span>
<span class="nc" id="L155">      }</span>

    private JavaFXSafeProxyCreator () {}

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
<span class="nc bnc" id="L164" title="All 6 branches missed.">    @RequiredArgsConstructor(access = PRIVATE)</span>
    public static final class NodeAndDelegate
      {
<span class="nc" id="L167">        @Getter @Nonnull</span>
        private final Node node;

        @Nonnull
        private final Object delegate;

        @Nonnull
        public &lt;T&gt; T getDelegate()
          {
<span class="nc" id="L176">            return (T)delegate;</span>
          }

        @Nonnull
        public static &lt;T&gt; NodeAndDelegate load (@Nonnull final Class&lt;T&gt; clazz, @Nonnull final String resource)
          throws IOException
          {
<span class="nc" id="L183">            log.debug(&quot;NodeAndDelegate({}, {})&quot;, clazz, resource);</span>
<span class="nc bnc" id="L184" title="All 4 branches missed.">            assert Platform.isFxApplicationThread() : &quot;Not in JavaFX UI Thread&quot;;</span>
<span class="nc" id="L185">            final FXMLLoader loader = new FXMLLoader(clazz.getResource(resource), null, null,</span>
<span class="nc" id="L186">                                                     type -&gt; ReflectionUtils.instantiateWithDependencies(type, BEANS));</span>
<span class="nc" id="L187">            final Node node = (Node)loader.load();</span>
<span class="nc" id="L188">            final T jfxController = loader.getController();</span>
<span class="nc" id="L189">            ReflectionUtils.injectDependencies(jfxController, BEANS);</span>
<span class="nc" id="L190">            final Class&lt;?&gt;[] interfaces = jfxController.getClass().getInterfaces();</span>

<span class="nc bnc" id="L192" title="All 2 branches missed.">            if (interfaces.length == 0)</span>
              {
<span class="nc" id="L194">                log.warn(&quot;{} has no interface: not creating safe proxy&quot;, jfxController.getClass());</span>
<span class="nc" id="L195">                log.debug(&quot;&gt;&gt;&gt;&gt; load({}, {}) completed&quot;, clazz, resource);</span>
<span class="nc" id="L196">                return new NodeAndDelegate(node, jfxController);</span>
              }
            else
              {
<span class="nc" id="L200">                final Class&lt;T&gt; interfaceClass = (Class&lt;T&gt;)interfaces[0]; // FIXME</span>
<span class="nc" id="L201">                final T safeDelegate = JavaFXSafeProxyCreator.createSafeProxy(jfxController, interfaceClass);</span>
<span class="nc" id="L202">                log.debug(&quot;&gt;&gt;&gt;&gt; load({}, {}) completed&quot;, clazz, resource);</span>
<span class="nc" id="L203">                return new NodeAndDelegate(node, safeDelegate);</span>
              }
          }
      }

    /*******************************************************************************************************************
     *
     * Creates a {@link NodeAndDelegate} for the given presentation class. The FXML resource name is inferred by
     * default, For instance, is the class is named {@code JavaFXFooBarPresentation}, the resource name is
     * {@code FooBar.fxml} and searched in the same packages as the class.
     *
     * @see #createNodeAndDelegate(java.lang.Class, java.lang.String)
     *
     * @since 1.0-ALPHA-13
     *
     * @param   presentationClass   the class of the presentation for which the resources must be created.
     *
     ******************************************************************************************************************/
    @Nonnull
    public static &lt;T&gt; NodeAndDelegate createNodeAndDelegate (@Nonnull final Class&lt;?&gt; presentationClass)
      {
<span class="nc" id="L224">        final String resource = presentationClass.getSimpleName().replaceAll(&quot;^JavaFX&quot;, &quot;&quot;)</span>
<span class="nc" id="L225">                                                                 .replaceAll(&quot;^JavaFx&quot;, &quot;&quot;)</span>
<span class="nc" id="L226">                                                                 .replaceAll(&quot;Presentation$&quot;, &quot;&quot;)</span>
                                                                 + &quot;.fxml&quot;;
<span class="nc" id="L228">        return createNodeAndDelegate(presentationClass, resource);</span>
      }

    /*******************************************************************************************************************
     *
     * Creates a {@link NodeAndDelegate} for the given presentation class.
     *
     * @param   presentationClass   the class of the presentation for which the resources must be created.
     * @param   fxmlResourcePath    the path of the FXML resource
     *
     ******************************************************************************************************************/
    @Nonnull
    public static &lt;T&gt; NodeAndDelegate createNodeAndDelegate (@Nonnull final Class&lt;?&gt; presentationClass,
                                                             @Nonnull final String fxmlResourcePath)
      {
<span class="nc" id="L243">        log.debug(&quot;createNodeAndDelegate({}, {})&quot;, presentationClass, fxmlResourcePath);</span>

<span class="nc" id="L245">        final CountDownLatch latch = new CountDownLatch(1);</span>
<span class="nc" id="L246">        final AtomicReference&lt;NodeAndDelegate&gt; nad = new AtomicReference&lt;&gt;();</span>
<span class="nc" id="L247">        final AtomicReference&lt;RuntimeException&gt; exception = new AtomicReference&lt;&gt;();</span>

<span class="nc bnc" id="L249" title="All 2 branches missed.">        if (Platform.isFxApplicationThread())</span>
          {
            try
              {
<span class="nc" id="L253">                return NodeAndDelegate.load(presentationClass, fxmlResourcePath);</span>
              }
<span class="nc" id="L255">            catch (IOException e)</span>
              {
<span class="nc" id="L257">                exception.set(new RuntimeException(e));</span>
              }
          }

<span class="nc" id="L261">        Platform.runLater(() -&gt;</span>
          {
            try
              {
<span class="nc" id="L265">                nad.set(NodeAndDelegate.load(presentationClass, fxmlResourcePath));</span>
              }
<span class="nc" id="L267">            catch (RuntimeException e)</span>
              {
<span class="nc" id="L269">                exception.set(e);</span>
              }
<span class="nc" id="L271">            catch (Exception e)</span>
              {
<span class="nc" id="L273">                exception.set(new RuntimeException(e));</span>
<span class="nc" id="L274">              }</span>

<span class="nc" id="L276">            latch.countDown();</span>
<span class="nc" id="L277">          });</span>

        try
          {
<span class="nc" id="L281">            log.debug(&quot;Waiting for NodeAndDelegate initialisation in JavaFX thread...&quot;);</span>
<span class="nc" id="L282">            log.debug(&quot;If deadlocks and you need longer time with the debugger, set {} (current value: {})&quot;,</span>
<span class="nc" id="L283">                      P_TIMEOUT, initializerTimeout);</span>
<span class="nc" id="L284">            latch.await(initializerTimeout, TimeUnit.SECONDS); // FIXME</span>
} <span class="nc" id="L286"> catch (InterruptedException e)</span> { <span class="nc" id="L288"> throw new RuntimeException(e);</span> <span class="nc" id="L289"> }</span> <span class="nc bnc" id="L291" title="All 2 branches missed."> if (exception.get() != null)</span> { <span class="nc" id="L293"> throw exception.get();</span> } <span class="nc bnc" id="L296" title="All 2 branches missed."> if (nad.get() == null)</span> { <span class="nc" id="L298"> final String message = String.format(&quot;Likely deadlock in the JavaFX Thread: couldn't create &quot; +</span> &quot;NodeAndDelegate: %s, %s&quot;, presentationClass, fxmlResourcePath); <span class="nc" id="L300"> throw new RuntimeException(message);</span> } <span class="nc" id="L303"> return nad.get();</span> } /******************************************************************************************************************* * * * ******************************************************************************************************************/ @Nonnull public static &lt;T&gt; T createSafeProxy (@Nonnull final T target, final Class&lt;T&gt; interfaceClass) { <span class="nc" id="L314"> return (T)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),</span> new Class[] { interfaceClass }, new JavaFXSafeProxy&lt;&gt;(target)); } } </pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.7.202105040129</span></div></body></html>