JavaFXSafeProxy.java

  1. /*
  2.  * *************************************************************************************************************************************************************
  3.  *
  4.  * SteelBlue: DCI User Interfaces
  5.  * http://tidalwave.it/projects/steelblue
  6.  *
  7.  * Copyright (C) 2015 - 2025 by Tidalwave s.a.s. (http://tidalwave.it)
  8.  *
  9.  * *************************************************************************************************************************************************************
  10.  *
  11.  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
  12.  * You may obtain a copy of the License at
  13.  *
  14.  *     http://www.apache.org/licenses/LICENSE-2.0
  15.  *
  16.  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  17.  * CONDITIONS OF ANY KIND, either express or implied.  See the License for the specific language governing permissions and limitations under the License.
  18.  *
  19.  * *************************************************************************************************************************************************************
  20.  *
  21.  * git clone https://bitbucket.org/tidalwave/steelblue-src
  22.  * git clone https://github.com/tidalwave-it/steelblue-src
  23.  *
  24.  * *************************************************************************************************************************************************************
  25.  */
  26. package it.tidalwave.ui.javafx.impl.util;

  27. import java.lang.reflect.InvocationHandler;
  28. import java.lang.reflect.Method;
  29. import jakarta.annotation.Nonnull;
  30. import java.util.concurrent.CountDownLatch;
  31. import java.util.concurrent.atomic.AtomicReference;
  32. import javafx.application.Platform;
  33. import it.tidalwave.ui.javafx.JavaFXSafeProxyCreator;
  34. import org.slf4j.LoggerFactory;
  35. import lombok.Getter;
  36. import lombok.RequiredArgsConstructor;
  37. import lombok.Setter;
  38. import lombok.extern.slf4j.Slf4j;

  39. /***************************************************************************************************************************************************************
  40.  *
  41.  * An {@link InvocationHandler} that safely wraps all method calls with {@link Platform#runLater(Runnable)}. The caller
  42.  * is not blocked if the method is declared as {@code void}; it is blocked otherwise, so it can immediately retrieve
  43.  * the result.
  44.  *
  45.  * This behaviour is required by {@link JavaFXSafeProxyCreator#createNodeAndDelegate(Class)} ()}.
  46.  *
  47.  * TODO: add support for aysnc returning a Future.
  48.  *
  49.  * @author  Fabrizio Giudici
  50.  *
  51.  **************************************************************************************************************************************************************/
  52. @RequiredArgsConstructor @Slf4j
  53. public class JavaFXSafeProxy<T> implements InvocationHandler
  54.   {
  55.     @Nonnull @Getter @Setter
  56.     private T delegate;

  57.     private final boolean logDelegateInvocations;

  58.     @Override
  59.     public Object invoke (@Nonnull final Object proxy, @Nonnull final Method method, @Nonnull final Object[] args)
  60.       throws Throwable
  61.       {
  62.         final var result = new AtomicReference<>();
  63.         final var throwable = new AtomicReference<Throwable>();
  64.         final var waitForReturn = new CountDownLatch(1);

  65.         JavaFXSafeRunner.runSafely(() ->
  66.           {
  67.             try
  68.               {
  69.                 if (logDelegateInvocations)
  70.                   {
  71.                     logInvocation(delegate.getClass(), method, args);
  72.                   }

  73.                 result.set(method.invoke(delegate, args));
  74.               }
  75.             catch (Throwable t)
  76.               {
  77.                 throwable.set(t);
  78.                 log.error("Exception while calling JavaFX", t);
  79.               }
  80.             finally
  81.               {
  82.                 waitForReturn.countDown();
  83.               }
  84.           });

  85.         log.trace(">>>> waiting for method completion");
  86.         waitForReturn.await();

  87.         // This is probably useless - void methods return asynchronously
  88.         if (throwable.get() != null)
  89.           {
  90.             throw throwable.get();
  91.           }

  92.         return method.getReturnType().equals(void.class) ? null : result.get();
  93.       }

  94.     /***********************************************************************************************************************************************************
  95.      *
  96.      **********************************************************************************************************************************************************/
  97.     private static void logInvocation (@Nonnull final Class<?> clazz, @Nonnull final Method method, @Nonnull final Object[] args)
  98.       {
  99.         final var logger = LoggerFactory.getLogger(clazz);

  100.         if (logger.isDebugEnabled())
  101.           {
  102.             final var builder = new StringBuilder();
  103.             builder.append(method.getName());
  104.             builder.append("(");
  105.             var separator = "";

  106.             for (final Object arg : args)
  107.               {
  108.                 builder.append(separator);
  109.                 builder.append(arg != null ? arg.toString() : null);
  110.                 separator = ", ";
  111.               }

  112.             builder.append(")");
  113.             logger.debug(builder.toString());
  114.           }
  115.       }

  116.   }