/*
* Licensed under the Apache License, Version 2.0 (the "License");
*
* 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 "AS IS" 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.
*
* Contributions from 2013-2017 where performed either by US government
* employees, or under US Veterans Health Administration contracts.
*
* US Veterans Health Administration contributions by government employees
* are work of the U.S. Government and are not subject to copyright
* protection in the United States. Portions contributed by government
* employees are USGovWork (17USC ยง105). Not subject to copyright.
*
* Contribution by contractors to the US Veterans Health Administration
* during this period are contractually contributed under the
* Apache License, Version 2.0.
*
* See: https://www.usa.gov/government-works
*
* Contributions prior to 2013:
*
* Copyright (C) International Health Terminology Standards Development Organisation.
* Licensed under the Apache License, Version 2.0.
*
*/
package sh.isaac.api.util;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
//~--- non-JDK imports --------------------------------------------------------
import javafx.geometry.Dimension2D;
import javafx.scene.image.Image;
import javafx.scene.input.Dragboard;
import javafx.scene.input.InputMethodRequests;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.paint.Color;
import javafx.scene.paint.ImagePattern;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.RadialGradient;
import javafx.scene.shape.PathElement;
import javafx.scene.shape.SVGPath;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
import javafx.scene.shape.StrokeType;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.stage.Modality;
import javafx.stage.StageStyle;
import javafx.stage.Window;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
//~--- JDK imports ------------------------------------------------------------
import com.sun.glass.ui.CommonDialogs.FileChooserResult;
import com.sun.javafx.embed.HostInterface;
import com.sun.javafx.geom.Path2D;
import com.sun.javafx.geom.Shape;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.javafx.perf.PerformanceTracker;
import com.sun.javafx.runtime.VersionInfo;
import com.sun.javafx.runtime.async.AsyncOperation;
import com.sun.javafx.runtime.async.AsyncOperationListener;
import com.sun.javafx.scene.text.HitInfo;
import com.sun.javafx.scene.text.TextLayoutFactory;
import com.sun.javafx.tk.AppletWindow;
import com.sun.javafx.tk.DummyToolkit;
import com.sun.javafx.tk.FileChooserType;
import com.sun.javafx.tk.FontLoader;
import com.sun.javafx.tk.ImageLoader;
import com.sun.javafx.tk.PlatformImage;
import com.sun.javafx.tk.RenderJob;
import com.sun.javafx.tk.ScreenConfigurationAccessor;
import com.sun.javafx.tk.TKClipboard;
import com.sun.javafx.tk.TKDragGestureListener;
import com.sun.javafx.tk.TKDragSourceListener;
import com.sun.javafx.tk.TKDropTargetListener;
import com.sun.javafx.tk.TKScene;
import com.sun.javafx.tk.TKScreenConfigurationListener;
import com.sun.javafx.tk.TKStage;
import com.sun.javafx.tk.TKSystemMenu;
import com.sun.javafx.tk.Toolkit;
import com.sun.javafx.tk.quantum.MasterTimer;
import com.sun.scenario.DelayedRunnable;
import com.sun.scenario.animation.AbstractMasterTimer;
import com.sun.scenario.effect.FilterContext;
import com.sun.scenario.effect.Filterable;
//~--- classes ----------------------------------------------------------------
/**
* A terrible hack to attempt to keep basic parts of JavaFX usable (tasks, for example) on a headless environment.
*
* To use, very early in the startup sequence, run something like this:
*
* if (GraphicsEnvironment.isHeadless())
* {
* HeadlessToolkit.installToolkit();
* }
*
* This class is mostly copied from {@link DummyToolkit} with parts copied from {@link com.sun.javafx.tk.quantum.QuantumToolkit}
*
* @author darmbrust
*/
@SuppressWarnings("restriction")
public class HeadlessToolkit
extends Toolkit {
/** The Constant log. */
private static final Logger log = LogManager.getLogger();
//~--- fields --------------------------------------------------------------
/** The toolkit running. */
private final AtomicBoolean toolkitRunning = new AtomicBoolean(false);
/** The tasks. */
LinkedBlockingQueue<Runnable> tasks = new LinkedBlockingQueue<>();
/** The context map. */
private final Map<Object, Object> contextMap = Collections.synchronizedMap(new HashMap<>());
//~--- methods -------------------------------------------------------------
/**
* Accumulate stroke bounds.
*
* @param shape the shape
* @param bbox the bbox
* @param type the type
* @param strokewidth the strokewidth
* @param cap the cap
* @param join the join
* @param miterLimit the miter limit
* @param tx the tx
*/
@Override
public void accumulateStrokeBounds(Shape shape,
float[] bbox,
StrokeType type,
double strokewidth,
StrokeLineCap cap,
StrokeLineJoin join,
float miterLimit,
BaseTransform tx) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Adds the render job.
*
* @param rj the rj
* @return the future
*/
@Override
public Future<?> addRenderJob(RenderJob rj) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Can start nested event loop.
*
* @return true, if successful
*/
@Override
public boolean canStartNestedEventLoop() {
throw new UnsupportedOperationException(
"Not supported yet."); // To change body of generated methods, choose Tools | Templates.
}
/**
* Close applet window.
*/
@Override
public void closeAppletWindow() {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Convert hit info to FX.
*
* @param hit the hit
* @return the hit info
*/
@Override
public HitInfo convertHitInfoToFX(Object hit) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Convert shape to FX path.
*
* @param shape the shape
* @return the path element[]
*/
@Override
public PathElement[] convertShapeToFXPath(Object shape) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Creates the applet window.
*
* @param parent the parent
* @param serverName the server name
* @return the applet window
*/
@Override
public AppletWindow createAppletWindow(long parent, String serverName) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Creates the performance tracker.
*
* @return the performance tracker
*/
@Override
public PerformanceTracker createPerformanceTracker() {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Creates the platform image.
*
* @param w the w
* @param h the h
* @return the platform image
*/
@Override
public PlatformImage createPlatformImage(int w, int h) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Creates the SVG path 2 D.
*
* @param svgpath the svgpath
* @return the path 2 D
*/
@Override
public Path2D createSVGPath2D(SVGPath svgpath) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Creates the SVG path object.
*
* @param svgpath the svgpath
* @return the object
*/
@Override
public Object createSVGPathObject(SVGPath svgpath) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Creates the stroked shape.
*
* @param shape the shape
* @param pgtype the pgtype
* @param strokewidth the strokewidth
* @param pgcap the pgcap
* @param pgjoin the pgjoin
* @param miterLimit the miter limit
* @param dashArray the dash array
* @param dashOffset the dash offset
* @return the shape
*/
@Override
public Shape createStrokedShape(Shape shape,
StrokeType pgtype,
double strokewidth,
StrokeLineCap pgcap,
StrokeLineJoin pgjoin,
float miterLimit,
float[] dashArray,
float dashOffset) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Creates the TK embedded stage.
*
* @param host the host
* @param acc the acc
* @return the TK stage
*/
@Override
public TKStage createTKEmbeddedStage(HostInterface host, AccessControlContext acc) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Creates the TK popup stage.
*
* @param peerWindow the peer window
* @param popupStyle the popup style
* @param owner the owner
* @param acc the acc
* @return the TK stage
*/
@Override
public TKStage createTKPopupStage(Window peerWindow,
StageStyle popupStyle,
TKStage owner,
AccessControlContext acc) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Creates the TK stage.
*
* @param peerWindow the peer window
* @param securityDialog the security dialog
* @param stageStyle the stage style
* @param primary the primary
* @param modality the modality
* @param owner the owner
* @param rtl the rtl
* @param acc the acc
* @return the TK stage
*/
@Override
public TKStage createTKStage(Window peerWindow,
boolean securityDialog,
StageStyle stageStyle,
boolean primary,
Modality modality,
TKStage owner,
boolean rtl,
AccessControlContext acc) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Defer.
*
* @param runnable the runnable
*/
@Override
public void defer(Runnable runnable) {
this.tasks.add(runnable);
}
/**
* Enable drop.
*
* @param s the s
* @param l the l
*/
@Override
public void enableDrop(TKScene s, TKDropTargetListener l) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Enter nested event loop.
*
* @param key the key
* @return the object
*/
@Override
public Object enterNestedEventLoop(Object key) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Exit nested event loop.
*
* @param key the key
* @param rval the rval
*/
@Override
public void exitNestedEventLoop(Object key, Object rval) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Image contains.
*
* @param image the image
* @param x the x
* @param y the y
* @return true, if successful
*/
@Override
public boolean imageContains(Object image, float x, float y) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Inits the.
*
* @return true, if successful
*/
@Override
public boolean init() {
return true;
}
/**
* Install input method requests.
*
* @param scene the scene
* @param requests the requests
*/
@Override
public void installInputMethodRequests(TKScene scene, InputMethodRequests requests) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* For running on a headless system only - hack this toolkit into place.
*/
public static void installToolkit() {
log.debug("installHeadlessToolkit begins");
try {
final Field f = Toolkit.class.getDeclaredField("TOOLKIT");
FortifyFun.fixAccessible(f); // f.setAccessible(true);
final Object currentToolkit = f.get(null);
if (currentToolkit == null) {
log.debug("Installing the headless toolkit via reflection.");
f.set(null, new HeadlessToolkit());
} else if (currentToolkit.getClass()
.getCanonicalName()
.equals(HeadlessToolkit.class.getCanonicalName())) {
// Just do nothing, if this code gets called twice
log.debug("The headless toolkit already appears to be installed, doing nothing.");
return;
} else {
throw new RuntimeException("A real Toolkit is already configured");
}
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
// Get the javafx.version and javafx.runtime.version from a preconstructed
// java class, VersionInfo, created at build time.
VersionInfo.setupSystemProperties();
return null;
});
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException("Failed trying to hack JavaFX for Headless!", e);
}
}
/**
* Load image.
*
* @param stream the stream
* @param width the width
* @param height the height
* @param preserveRatio the preserve ratio
* @param smooth the smooth
* @return the image loader
*/
@Override
public ImageLoader loadImage(InputStream stream, int width, int height, boolean preserveRatio, boolean smooth) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Load image.
*
* @param url the url
* @param width the width
* @param height the height
* @param preserveRatio the preserve ratio
* @param smooth the smooth
* @return the image loader
*/
@Override
public ImageLoader loadImage(String url, int width, int height, boolean preserveRatio, boolean smooth) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Load image async.
*
* @param listener the listener
* @param url the url
* @param width the width
* @param height the height
* @param preserveRatio the preserve ratio
* @param smooth the smooth
* @return the async operation
*/
@Override
public AsyncOperation loadImageAsync(AsyncOperationListener<? extends ImageLoader> listener,
String url,
int width,
int height,
boolean preserveRatio,
boolean smooth) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Load platform image.
*
* @param platformImage the platform image
* @return the image loader
*/
@Override
public ImageLoader loadPlatformImage(Object platformImage) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Register drag gesture listener.
*
* @param s the s
* @param tms the tms
* @param l the l
*/
@Override
public void registerDragGestureListener(TKScene s, Set<TransferMode> tms, TKDragGestureListener l) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Render to image.
*
* @param context the context
* @return the object
*/
@Override
public Object renderToImage(ImageRenderingContext context) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Request next pulse.
*/
@Override
public void requestNextPulse() {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Show directory chooser.
*
* @param ownerWindow the owner window
* @param title the title
* @param initialDirectory the initial directory
* @return the file
*/
@Override
public File showDirectoryChooser(TKStage ownerWindow, String title, File initialDirectory) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Show file chooser.
*
* @param ownerWindow the owner window
* @param title the title
* @param initialDirectory the initial directory
* @param initialFileName the initial file name
* @param fileChooserType the file chooser type
* @param extensionFilters the extension filters
* @param selectedFilter the selected filter
* @return the file chooser result
*/
@Override
public FileChooserResult showFileChooser(TKStage ownerWindow,
String title,
File initialDirectory,
String initialFileName,
FileChooserType fileChooserType,
List<ExtensionFilter> extensionFilters,
ExtensionFilter selectedFilter) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Start drag.
*
* @param scene the scene
* @param tms the tms
* @param l the l
* @param dragboard the dragboard
*/
@Override
public void startDrag(TKScene scene, Set<TransferMode> tms, TKDragSourceListener l, Dragboard dragboard) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Startup.
*
* @param runnable the runnable
*/
@Override
public void startup(Runnable runnable) {
log.info("HeadlessTookit startup method called");
if (!this.toolkitRunning.getAndSet(true)) {
log.info("Starting a stand-in JavaFX Application Thread");
final Thread t = new Thread(() -> {
final Thread user = Thread.currentThread();
user.setName("JavaFX Application Thread");
// Set context class loader to the same as the thread that called startup
user.setContextClassLoader(user.getContextClassLoader());
setFxUserThread(user);
while (true) {
try {
this.tasks.take()
.run();
} catch (final Exception e) {
// don't care
}
}
});
t.setDaemon(true);
t.start();
}
this.tasks.add(runnable);
}
/**
* Stroke contains.
*
* @param shape the shape
* @param x the x
* @param y the y
* @param type the type
* @param strokewidth the strokewidth
* @param cap the cap
* @param join the join
* @param miterLimit the miter limit
* @return true, if successful
*/
@Override
public boolean strokeContains(Shape shape,
double x,
double y,
StrokeType type,
double strokewidth,
StrokeLineCap cap,
StrokeLineJoin join,
float miterLimit) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* To filterable.
*
* @param img the img
* @return the filterable
*/
@Override
public Filterable toFilterable(Image img) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Wait for.
*
* @param t the t
*/
@Override
public void waitFor(Task t) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Creates the color paint.
*
* @param paint the paint
* @return the object
*/
@Override
protected Object createColorPaint(Color paint) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Creates the image pattern paint.
*
* @param paint the paint
* @return the object
*/
@Override
protected Object createImagePatternPaint(ImagePattern paint) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Creates the linear gradient paint.
*
* @param paint the paint
* @return the object
*/
@Override
protected Object createLinearGradientPaint(LinearGradient paint) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Creates the radial gradient paint.
*
* @param paint the paint
* @return the object
*/
@Override
protected Object createRadialGradientPaint(RadialGradient paint) {
throw new UnsupportedOperationException("Not supported yet.");
}
//~--- set methods ---------------------------------------------------------
/**
* Sets the animation runnable.
*
* @param animationRunnable the new animation runnable
*/
@Override
public void setAnimationRunnable(DelayedRunnable animationRunnable) {
if (animationRunnable != null) {
this.tasks.add(animationRunnable);
}
}
//~--- get methods ---------------------------------------------------------
/**
* Checks if backward traversal key.
*
* @param e the e
* @return true, if backward traversal key
*/
@Override
public boolean isBackwardTraversalKey(KeyEvent e) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Gets the best cursor size.
*
* @param preferredWidth the preferred width
* @param preferredHeight the preferred height
* @return the best cursor size
*/
@Override
public Dimension2D getBestCursorSize(int preferredWidth, int preferredHeight) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Gets the context map.
*
* @return the context map
*/
@Override
public Map<Object, Object> getContextMap() {
return this.contextMap;
}
/**
* Gets the filter context.
*
* @param config the config
* @return the filter context
*/
@Override
public FilterContext getFilterContext(Object config) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Gets the font loader.
*
* @return the font loader
*/
@Override
public FontLoader getFontLoader() {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Checks if forward traversal key.
*
* @param e the e
* @return true, if forward traversal key
*/
@Override
public boolean isForwardTraversalKey(KeyEvent e) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Gets the key code for char.
*
* @param character the character
* @return the key code for char
*/
@Override
public int getKeyCodeForChar(String character) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Gets the master timer.
*
* @return the master timer
*/
@Override
public AbstractMasterTimer getMasterTimer() {
return MasterTimer.getInstance();
}
/**
* Gets the maximum cursor colors.
*
* @return the maximum cursor colors
*/
@Override
public int getMaximumCursorColors() {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Gets the multi click max X.
*
* @return the multi click max X
*/
@Override
public int getMultiClickMaxX() {
return 0;
}
/**
* Gets the multi click max Y.
*
* @return the multi click max Y
*/
@Override
public int getMultiClickMaxY() {
return 0;
}
/**
* Gets the multi click time.
*
* @return the multi click time
*/
@Override
public long getMultiClickTime() {
return 0L;
}
/**
* Gets the named clipboard.
*
* @param name the name
* @return the named clipboard
*/
@Override
public TKClipboard getNamedClipboard(String name) {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Checks if nested loop running.
*
* @return true, if nested loop running
*/
@Override
public boolean isNestedLoopRunning() {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Gets the performance tracker.
*
* @return the performance tracker
*/
@Override
public PerformanceTracker getPerformanceTracker() {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Gets the platform shortcut key.
*
* @return the platform shortcut key
*/
@Override
public KeyCode getPlatformShortcutKey() {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Gets the primary screen.
*
* @return the primary screen
*/
@Override
public Object getPrimaryScreen() {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Gets the refresh rate.
*
* @return the refresh rate
*/
@Override
public int getRefreshRate() {
return 60;
}
/**
* Gets the screen configuration accessor.
*
* @return the screen configuration accessor
*/
@Override
public ScreenConfigurationAccessor getScreenConfigurationAccessor() {
throw new UnsupportedOperationException(
"Not supported yet."); // To change body of generated methods, choose Tools | Templates.
}
//~--- set methods ---------------------------------------------------------
/**
* Set screen configuration listener.
*
* @param listener the listener
* @return the screen configuration accessor
*/
@Override
public ScreenConfigurationAccessor setScreenConfigurationListener(TKScreenConfigurationListener listener) {
throw new UnsupportedOperationException("Not supported yet.");
}
//~--- get methods ---------------------------------------------------------
/**
* Gets the screens.
*
* @return the screens
*/
@Override
public List<?> getScreens() {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Gets the system clipboard.
*
* @return the system clipboard
*/
@Override
public com.sun.javafx.tk.TKClipboard getSystemClipboard() {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Gets the system menu.
*
* @return the system menu
*/
@Override
public TKSystemMenu getSystemMenu() {
throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Gets the text layout factory.
*
* @return the text layout factory
*/
@Override
public TextLayoutFactory getTextLayoutFactory() {
throw new UnsupportedOperationException("Not supported yet.");
}
}