JavaFXUtils.java
0001 /*
0002  * Copyright 2008-2017 the original author or authors.
0003  *
0004  * Licensed under the Apache License, Version 2.0 (the "License");
0005  * you may not use this file except in compliance with the License.
0006  * You may obtain a copy of the License at
0007  *
0008  *     http://www.apache.org/licenses/LICENSE-2.0
0009  *
0010  * Unless required by applicable law or agreed to in writing, software
0011  * distributed under the License is distributed on an "AS IS" BASIS,
0012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013  * See the License for the specific language governing permissions and
0014  * limitations under the License.
0015  */
0016 package griffon.javafx.support;
0017 
0018 import griffon.core.GriffonApplication;
0019 import griffon.core.artifact.GriffonController;
0020 import griffon.core.controller.Action;
0021 import griffon.core.controller.ActionManager;
0022 import griffon.core.editors.ValueConversionException;
0023 import griffon.core.i18n.MessageSource;
0024 import griffon.exceptions.InstanceMethodInvocationException;
0025 import griffon.javafx.collections.GriffonFXCollections;
0026 import javafx.application.Platform;
0027 import javafx.beans.property.Property;
0028 import javafx.collections.ObservableList;
0029 import javafx.collections.ObservableMap;
0030 import javafx.collections.ObservableSet;
0031 import javafx.event.ActionEvent;
0032 import javafx.event.EventHandler;
0033 import javafx.scene.Node;
0034 import javafx.scene.Parent;
0035 import javafx.scene.Scene;
0036 import javafx.scene.control.Accordion;
0037 import javafx.scene.control.ButtonBar;
0038 import javafx.scene.control.ButtonBase;
0039 import javafx.scene.control.CheckBox;
0040 import javafx.scene.control.CheckMenuItem;
0041 import javafx.scene.control.ContextMenu;
0042 import javafx.scene.control.Control;
0043 import javafx.scene.control.Labeled;
0044 import javafx.scene.control.Menu;
0045 import javafx.scene.control.MenuBar;
0046 import javafx.scene.control.MenuItem;
0047 import javafx.scene.control.RadioButton;
0048 import javafx.scene.control.RadioMenuItem;
0049 import javafx.scene.control.ScrollPane;
0050 import javafx.scene.control.SplitPane;
0051 import javafx.scene.control.Tab;
0052 import javafx.scene.control.TabPane;
0053 import javafx.scene.control.TitledPane;
0054 import javafx.scene.control.ToggleButton;
0055 import javafx.scene.control.ToolBar;
0056 import javafx.scene.control.Tooltip;
0057 import javafx.scene.image.Image;
0058 import javafx.scene.image.ImageView;
0059 import javafx.stage.Window;
0060 
0061 import javax.annotation.Nonnull;
0062 import javax.annotation.Nullable;
0063 import java.lang.reflect.Constructor;
0064 import java.lang.reflect.InvocationTargetException;
0065 import java.net.URL;
0066 import java.util.Collection;
0067 import java.util.LinkedHashSet;
0068 import java.util.Map;
0069 import java.util.Set;
0070 import java.util.function.Predicate;
0071 
0072 import static griffon.core.GriffonApplication.PROPERTY_LOCALE;
0073 import static griffon.util.GriffonClassUtils.EMPTY_OBJECT_ARRAY;
0074 import static griffon.util.GriffonClassUtils.getGetterName;
0075 import static griffon.util.GriffonClassUtils.getPropertyValue;
0076 import static griffon.util.GriffonClassUtils.invokeExactInstanceMethod;
0077 import static griffon.util.GriffonClassUtils.invokeInstanceMethod;
0078 import static griffon.util.GriffonNameUtils.isBlank;
0079 import static griffon.util.GriffonNameUtils.requireNonBlank;
0080 import static java.util.Objects.isNull;
0081 import static java.util.Objects.requireNonNull;
0082 
0083 /**
0084  @author Andres Almiray
0085  */
0086 public final class JavaFXUtils {
0087     private static final String ERROR_NODE_NULL = "Argument 'node' must not be null";
0088     private static final String ERROR_CONTROL_NULL = "Argument 'control' must not be null";
0089     private static final String ERROR_ACTION_NULL = "Argument 'action' must not be null";
0090     private static final String ERROR_ICON_BLANK = "Argument 'iconUrl' must not be blank";
0091     private static final String ERROR_ID_BLANK = "Argument 'id' must not be blank";
0092     private static final String ERROR_URL_BLANK = "Argument 'url' must not be blank";
0093     private static final String ERROR_KEY_BLANK = "Argument 'key' must not be blank";
0094     private static final String ERROR_ARGS_BLANK = "Argument 'args' must not be blank";
0095     private static final String ERROR_ROOT_NULL = "Argument 'root' must not be null";
0096     private static final String ERROR_PREDICATE_NULL = "Argument 'predicate' must not be null";
0097     private static final String ERROR_CONTROLLER_NULL = "Argument 'controller' must not be null";
0098     private static final String ERROR_APPLICATION_NULL = "Argument 'application' must not be null";
0099     private static final String PROPERTY_SUFFIX = "Property";
0100     private static final String SUFFIX_KEY = "-KEY";
0101     private static final String SUFFIX_ARGS = "-ARGS";
0102     private static final String SUFFIX_DEFAULT_VALUE = "-DEFAULT_VALUE";
0103     private static final ActionMatcher DEFAULT_ACTION_MATCHER = ActionMatcher.DEFAULT;
0104 
0105     private JavaFXUtils() {
0106 
0107     }
0108 
0109     /**
0110      * Finds out if an i18n {@code key} has been registered with the target {@code Node}, returning the key if found.
0111      *
0112      @param node the target node on which the key may have been registered.
0113      *
0114      @return the key registered with the target {@code Node} or {@code null} if not found.
0115      *
0116      @since 2.9.0
0117      */
0118     @Nullable
0119     public static String getI18nKey(@Nonnull Labeled node) {
0120         requireNonNull(node, ERROR_NODE_NULL);
0121         return (Stringnode.getProperties().get(MessageSource.class.getName() + SUFFIX_KEY);
0122     }
0123 
0124     /**
0125      * Associates an i18n arrays of arguments to a {@code node}.
0126      * These arguments will be used alongside a key to resolve a message via the application's {@code MessageSource}.
0127      *
0128      @param node the target node on which the key will be registered.
0129      @param args the array of arguments to be registered.
0130      *
0131      @since 2.9.0
0132      */
0133     public static void setI18nArgs(@Nonnull Labeled node, @Nullable String args) {
0134         requireNonNull(node, ERROR_NODE_NULL);
0135         requireNonBlank(args, ERROR_ARGS_BLANK);
0136         node.getProperties().put(MessageSource.class.getName() + SUFFIX_ARGS, args);
0137     }
0138 
0139     /**
0140      * Finds out if an {@code arguments array} has been registered with the target {@code Node}, returning the array if found.
0141      *
0142      @param node the target node on which the arguments may have been registered.
0143      *
0144      @return the arguments registered with the target {@code Node} or {@code null} if not found.
0145      *
0146      @since 2.9.0
0147      */
0148     @Nullable
0149     public static String getI18nArgs(@Nonnull Labeled node) {
0150         requireNonNull(node, ERROR_NODE_NULL);
0151         return (Stringnode.getProperties().get(MessageSource.class.getName() + SUFFIX_ARGS);
0152     }
0153 
0154     /**
0155      * Associates an default value {@code node}.
0156      * The value will be used alongside a key to resolve a message via the application's {@code MessageSource}.
0157      *
0158      @param node         the target node on which the key will be registered.
0159      @param defaultValue the value to be registered.
0160      *
0161      @since 2.9.0
0162      */
0163     public static void setI18nDefaultValue(@Nonnull Labeled node, @Nullable String defaultValue) {
0164         requireNonNull(node, ERROR_NODE_NULL);
0165         node.getProperties().put(MessageSource.class.getName() + SUFFIX_DEFAULT_VALUE, defaultValue);
0166     }
0167 
0168     /**
0169      * Finds out if a {@code default value} has been registered with the target {@code Node}, returning the value if found.
0170      *
0171      @param node the target node on which the value may have been registered.
0172      *
0173      @return the value registered with the target {@code Node} or {@code null} if not found.
0174      *
0175      @since 2.9.0
0176      */
0177     @Nullable
0178     public static String getI18nDefaultValue(@Nonnull Labeled node) {
0179         requireNonNull(node, ERROR_NODE_NULL);
0180         return (Stringnode.getProperties().get(MessageSource.class.getName() + SUFFIX_DEFAULT_VALUE);
0181     }
0182 
0183     /**
0184      * Associates an i18n key to a {@code node}. The key is used to resolve a message via the application's {@code MessageSource}.
0185      *
0186      @param node the target node on which the key will be registered.
0187      @param key  the message key to be registered.
0188      *
0189      @since 2.9.0
0190      */
0191     public static void setI18nKey(@Nonnull Tab node, @Nonnull String key) {
0192         requireNonNull(node, ERROR_NODE_NULL);
0193         requireNonBlank(key, ERROR_KEY_BLANK);
0194         node.getProperties().put(MessageSource.class.getName() + SUFFIX_KEY, key);
0195     }
0196 
0197     /**
0198      * Finds out if an i18n {@code key} has been registered with the target {@code Node}, returning the key if found.
0199      *
0200      @param node the target node on which the key may have been registered.
0201      *
0202      @return the key registered with the target {@code Node} or {@code null} if not found.
0203      *
0204      @since 2.9.0
0205      */
0206     @Nullable
0207     public static String getI18nKey(@Nonnull Tab node) {
0208         requireNonNull(node, ERROR_NODE_NULL);
0209         return (Stringnode.getProperties().get(MessageSource.class.getName() + SUFFIX_KEY);
0210     }
0211 
0212     /**
0213      * Associates an i18n arrays of arguments to a {@code node}.
0214      * These arguments will be used alongside a key to resolve a message via the application's {@code MessageSource}.
0215      *
0216      @param node the target node on which the key will be registered.
0217      @param args the array of arguments to be registered.
0218      *
0219      @since 2.9.0
0220      */
0221     public static void setI18nArgs(@Nonnull Tab node, @Nullable String args) {
0222         requireNonNull(node, ERROR_NODE_NULL);
0223         requireNonBlank(args, ERROR_ARGS_BLANK);
0224         node.getProperties().put(MessageSource.class.getName() + SUFFIX_ARGS, args);
0225     }
0226 
0227     /**
0228      * Finds out if an {@code arguments array} has been registered with the target {@code Node}, returning the array if found.
0229      *
0230      @param node the target node on which the arguments may have been registered.
0231      *
0232      @return the arguments registered with the target {@code Node} or {@code null} if not found.
0233      *
0234      @since 2.9.0
0235      */
0236     @Nullable
0237     public static String getI18nArgs(@Nonnull Tab node) {
0238         requireNonNull(node, ERROR_NODE_NULL);
0239         return (Stringnode.getProperties().get(MessageSource.class.getName() + SUFFIX_ARGS);
0240     }
0241 
0242     /**
0243      * Associates an default value {@code node}.
0244      * The value will be used alongside a key to resolve a message via the application's {@code MessageSource}.
0245      *
0246      @param node         the target node on which the key will be registered.
0247      @param defaultValue the value to be registered.
0248      *
0249      @since 2.9.0
0250      */
0251     public static void setI18nDefaultValue(@Nonnull Tab node, @Nullable String defaultValue) {
0252         requireNonNull(node, ERROR_NODE_NULL);
0253         node.getProperties().put(MessageSource.class.getName() + SUFFIX_DEFAULT_VALUE, defaultValue);
0254     }
0255 
0256     /**
0257      * Finds out if a {@code default value} has been registered with the target {@code Node}, returning the value if found.
0258      *
0259      @param node the target node on which the value may have been registered.
0260      *
0261      @return the value registered with the target {@code Node} or {@code null} if not found.
0262      *
0263      @since 2.9.0
0264      */
0265     @Nullable
0266     public static String getI18nDefaultValue(@Nonnull Tab node) {
0267         requireNonNull(node, ERROR_NODE_NULL);
0268         return (Stringnode.getProperties().get(MessageSource.class.getName() + SUFFIX_DEFAULT_VALUE);
0269     }
0270 
0271     /**
0272      * Associates an i18n key to a {@code node}. The key is used to resolve a message via the application's {@code MessageSource}.
0273      *
0274      @param node the target node on which the key will be registered.
0275      @param key  the message key to be registered.
0276      *
0277      @since 2.9.0
0278      */
0279     public static void setI18nKey(@Nonnull MenuItem node, @Nonnull String key) {
0280         requireNonNull(node, ERROR_NODE_NULL);
0281         requireNonBlank(key, ERROR_KEY_BLANK);
0282         node.getProperties().put(MessageSource.class.getName() + SUFFIX_KEY, key);
0283     }
0284 
0285     /**
0286      * Finds out if an i18n {@code key} has been registered with the target {@code Node}, returning the key if found.
0287      *
0288      @param node the target node on which the key may have been registered.
0289      *
0290      @return the key registered with the target {@code Node} or {@code null} if not found.
0291      *
0292      @since 2.9.0
0293      */
0294     @Nullable
0295     public static String getI18nKey(@Nonnull MenuItem node) {
0296         requireNonNull(node, ERROR_NODE_NULL);
0297         return (Stringnode.getProperties().get(MessageSource.class.getName() + SUFFIX_KEY);
0298     }
0299 
0300     /**
0301      * Associates an i18n arrays of arguments to a {@code node}.
0302      * These arguments will be used alongside a key to resolve a message via the application's {@code MessageSource}.
0303      *
0304      @param node the target node on which the key will be registered.
0305      @param args the array of arguments to be registered.
0306      *
0307      @since 2.9.0
0308      */
0309     public static void setI18nArgs(@Nonnull MenuItem node, @Nullable String args) {
0310         requireNonNull(node, ERROR_NODE_NULL);
0311         requireNonBlank(args, ERROR_ARGS_BLANK);
0312         node.getProperties().put(MessageSource.class.getName() + SUFFIX_ARGS, args);
0313     }
0314 
0315     /**
0316      * Finds out if an {@code arguments array} has been registered with the target {@code Node}, returning the array if found.
0317      *
0318      @param node the target node on which the arguments may have been registered.
0319      *
0320      @return the arguments registered with the target {@code Node} or {@code null} if not found.
0321      *
0322      @since 2.9.0
0323      */
0324     @Nullable
0325     public static String getI18nArgs(@Nonnull MenuItem node) {
0326         requireNonNull(node, ERROR_NODE_NULL);
0327         return (Stringnode.getProperties().get(MessageSource.class.getName() + SUFFIX_ARGS);
0328     }
0329 
0330     /**
0331      * Associates an default value {@code node}.
0332      * The value will be used alongside a key to resolve a message via the application's {@code MessageSource}.
0333      *
0334      @param node         the target node on which the key will be registered.
0335      @param defaultValue the value to be registered.
0336      *
0337      @since 2.9.0
0338      */
0339     public static void setI18nDefaultValue(@Nonnull MenuItem node, @Nullable String defaultValue) {
0340         requireNonNull(node, ERROR_NODE_NULL);
0341         node.getProperties().put(MessageSource.class.getName() + SUFFIX_DEFAULT_VALUE, defaultValue);
0342     }
0343 
0344     /**
0345      * Finds out if a {@code default value} has been registered with the target {@code Node}, returning the value if found.
0346      *
0347      @param node the target node on which the value may have been registered.
0348      *
0349      @return the value registered with the target {@code Node} or {@code null} if not found.
0350      *
0351      @since 2.9.0
0352      */
0353     @Nullable
0354     public static String getI18nDefaultValue(@Nonnull MenuItem node) {
0355         requireNonNull(node, ERROR_NODE_NULL);
0356         return (Stringnode.getProperties().get(MessageSource.class.getName() + SUFFIX_DEFAULT_VALUE);
0357     }
0358 
0359     public static void connectMessageSource(@Nonnull Object node, @Nonnull GriffonApplication application) {
0360         requireNonNull(node, ERROR_NODE_NULL);
0361         requireNonNull(application, ERROR_APPLICATION_NULL);
0362 
0363         findElements(node, arg -> (arg instanceof Labeled && !isBlank(getI18nKey((Labeledarg))) ||
0364             (arg instanceof Tab && !isBlank(getI18nKey((Tabarg))) ||
0365             (arg instanceof MenuItem && !isBlank(getI18nKey((MenuItemarg))))
0366             .forEach(element -> {
0367                 if (element instanceof Labeled) {
0368                     doConnectMessageSource((Labeledelement, application);
0369                 else if (element instanceof Tab) {
0370                     doConnectMessageSource((Tabelement, application);
0371                 else if (element instanceof MenuItem) {
0372                     doConnectMessageSource((MenuItemelement, application);
0373                 }
0374             });
0375     }
0376 
0377     private static void doConnectMessageSource(@Nonnull final Labeled node, @Nonnull final GriffonApplication application) {
0378         application.addPropertyChangeListener(PROPERTY_LOCALE, evt -> updateLabeled(node, application));
0379         updateLabeled(node, application);
0380     }
0381 
0382     private static void doConnectMessageSource(@Nonnull final Tab node, @Nonnull final GriffonApplication application) {
0383         application.addPropertyChangeListener(PROPERTY_LOCALE, evt -> updateLabeled(node, application));
0384         updateLabeled(node, application);
0385     }
0386 
0387     private static void doConnectMessageSource(@Nonnull final MenuItem node, @Nonnull final GriffonApplication application) {
0388         application.addPropertyChangeListener(PROPERTY_LOCALE, evt -> updateLabeled(node, application));
0389         updateLabeled(node, application);
0390     }
0391 
0392     private static void updateLabeled(@Nonnull final Labeled node, @Nonnull final GriffonApplication application) {
0393         runInsideUIThread(() -> {
0394             String key = getI18nKey(node);
0395             String args = getI18nArgs(node);
0396             String defaultValue = getI18nDefaultValue(node);
0397 
0398             Object[] argArray = isBlank(args? EMPTY_OBJECT_ARRAY : args.split(",");
0399 
0400             if (isBlank(defaultValue)) {
0401                 node.setText(application.getMessageSource().getMessage(key, argArray, application.getLocale()));
0402             else {
0403                 node.setText(application.getMessageSource().getMessage(key, argArray, application.getLocale(), defaultValue));
0404             }
0405         });
0406     }
0407 
0408     private static void updateLabeled(@Nonnull final Tab node, @Nonnull final GriffonApplication application) {
0409         runInsideUIThread(() -> {
0410             String key = getI18nKey(node);
0411             String args = getI18nArgs(node);
0412             String defaultValue = getI18nDefaultValue(node);
0413 
0414             Object[] argArray = isBlank(args? EMPTY_OBJECT_ARRAY : args.split(",");
0415 
0416             if (isBlank(defaultValue)) {
0417                 node.setText(application.getMessageSource().getMessage(key, argArray, application.getLocale()));
0418             else {
0419                 node.setText(application.getMessageSource().getMessage(key, argArray, application.getLocale(), defaultValue));
0420             }
0421         });
0422     }
0423 
0424     private static void updateLabeled(@Nonnull final MenuItem node, @Nonnull final GriffonApplication application) {
0425         runInsideUIThread(() -> {
0426             String key = getI18nKey(node);
0427             String args = getI18nArgs(node);
0428             String defaultValue = getI18nDefaultValue(node);
0429 
0430             Object[] argArray = isBlank(args? EMPTY_OBJECT_ARRAY : args.split(",");
0431 
0432             if (isBlank(defaultValue)) {
0433                 node.setText(application.getMessageSource().getMessage(key, argArray, application.getLocale()));
0434             else {
0435                 node.setText(application.getMessageSource().getMessage(key, argArray, application.getLocale(), defaultValue));
0436             }
0437         });
0438     }
0439 
0440     /**
0441      * Associates a {@code Action} with a target {@code Node}.
0442      *
0443      @param node     the target node on which the action will be registered.
0444      @param actionId the id of the action to be registered.
0445      *
0446      @since 2.8.0
0447      */
0448     public static void setGriffonActionId(@Nonnull Node node, @Nonnull String actionId) {
0449         requireNonNull(node, ERROR_NODE_NULL);
0450         requireNonBlank(actionId, ERROR_ID_BLANK);
0451         node.getProperties().put(Action.class.getName(), actionId);
0452     }
0453 
0454     /**
0455      * Finds out if an {@code Action} has been registered with the target {@code Node}, returning the action id if found.
0456      *
0457      @param node the target node on which the action may have been registered.
0458      *
0459      @return the name of the action registered with the target {@code Node} or {@code null} if not found.
0460      *
0461      @since 2.8.0
0462      */
0463     @Nullable
0464     public static String getGriffonActionId(@Nonnull Node node) {
0465         requireNonNull(node, ERROR_NODE_NULL);
0466         return (Stringnode.getProperties().get(Action.class.getName());
0467     }
0468 
0469     /**
0470      * Associates a {@code Action} with a target {@code MenuItem}.
0471      *
0472      @param menuItem the target menuItem on which the action will be registered.
0473      @param actionId the id of the action to be registered.
0474      *
0475      @since 2.8.0
0476      */
0477     public static void setGriffonActionId(@Nonnull MenuItem menuItem, @Nonnull String actionId) {
0478         requireNonNull(menuItem, ERROR_NODE_NULL);
0479         requireNonBlank(actionId, ERROR_ID_BLANK);
0480         menuItem.getProperties().put(Action.class.getName(), actionId);
0481     }
0482 
0483     /**
0484      * Finds out if an {@code Action} has been registered with the target {@code MenuItem}, returning the action id if found.
0485      *
0486      @param menuItem the target menuItem on which the action may have been registered.
0487      *
0488      @return the name of the action registered with the target {@code MenuItem} or {@code null} if not found.
0489      *
0490      @since 2.8.0
0491      */
0492     @Nullable
0493     public static String getGriffonActionId(@Nonnull MenuItem menuItem) {
0494         requireNonNull(menuItem, ERROR_NODE_NULL);
0495         return (StringmenuItem.getProperties().get(Action.class.getName());
0496     }
0497 
0498     /**
0499      * Wraps an <tt>ObservableList</tt>, publishing updates inside the UI thread.
0500      *
0501      @param source the <tt>ObservableList</tt> to be wrapped
0502      @param <E>    the list's parameter type.
0503      *
0504      @return a new <tt>ObservableList</tt>
0505      *
0506      @see GriffonFXCollections#uiThreadAwareObservableList
0507      @since 2.6.0
0508      @deprecated Use {@code GriffonFXCollections.uiThreadAwareObservableList} instead.
0509      */
0510     @Deprecated
0511     @Nonnull
0512     public static <E> ObservableList<E> createJavaFXThreadProxyList(@Nonnull ObservableList<E> source) {
0513         return GriffonFXCollections.uiThreadAwareObservableList(source);
0514     }
0515 
0516     /**
0517      * Wraps an <tt>ObservableSet</tt>, publishing updates inside the UI thread.
0518      *
0519      @param source the <tt>ObservableSet</tt> to be wrapped
0520      @param <E>    the set's parameter type.
0521      *
0522      @return a new <tt>ObservableSet</tt>
0523      *
0524      @see GriffonFXCollections#uiThreadAwareObservableSet
0525      @since 2.9.0
0526      @deprecated Use {@code GriffonFXCollections.uiThreadAwareObservableSet} instead.
0527      */
0528     @Deprecated
0529     @Nonnull
0530     public static <E> ObservableSet<E> createJavaFXThreadProxySet(@Nonnull ObservableSet<E> source) {
0531         return GriffonFXCollections.uiThreadAwareObservableSet(source);
0532     }
0533 
0534     /**
0535      * Wraps an <tt>ObservableMap</tt>, publishing updates inside the UI thread.
0536      *
0537      @param source the <tt>ObservableMap</tt> to be wrapped
0538      @param <K>    the type of keys maintained by the map
0539      @param <V>    the type of mapped values
0540      *
0541      @return a new <tt>ObservableMap</tt>
0542      *
0543      @see GriffonFXCollections#uiThreadAwareObservableMap
0544      @since 2.9.0
0545      @deprecated Use {@code GriffonFXCollections.uiThreadAwareObservableMap} instead.
0546      */
0547     @Deprecated
0548     @Nonnull
0549     public static <K, V> ObservableMap<K, V> createJavaFXThreadProxyMap(@Nonnull ObservableMap<K, V> source) {
0550         return GriffonFXCollections.uiThreadAwareObservableMap(source);
0551     }
0552 
0553     @Nonnull
0554     @SuppressWarnings("ConstantConditions")
0555     public static <B> Property<?> extractProperty(@Nonnull B bean, @Nonnull String propertyName) {
0556         requireNonNull(bean, "Argument 'bean' must not be null");
0557         requireNonBlank(propertyName, "Argument 'propertyName' must not be null");
0558 
0559         if (!propertyName.endsWith(PROPERTY_SUFFIX)) {
0560             propertyName += PROPERTY_SUFFIX;
0561         }
0562 
0563         InstanceMethodInvocationException imie;
0564         try {
0565             // 1. try <columnName>Property() first
0566             return (Property<?>invokeExactInstanceMethod(bean, propertyName);
0567         catch (InstanceMethodInvocationException e) {
0568             imie = e;
0569         }
0570 
0571         // 2. fallback to get<columnName>Property()
0572         try {
0573             return (Property<?>invokeExactInstanceMethod(bean, getGetterName(propertyName));
0574         catch (InstanceMethodInvocationException e) {
0575             throw imie;
0576         }
0577     }
0578 
0579     public static void connectActions(@Nonnull Object node, @Nonnull GriffonController controller, @Nonnull ActionMatcher actionMatcher) {
0580         requireNonNull(node, ERROR_NODE_NULL);
0581         requireNonNull(controller, ERROR_CONTROLLER_NULL);
0582         actionMatcher = actionMatcher != null ? actionMatcher : DEFAULT_ACTION_MATCHER;
0583         ActionManager actionManager = controller.getApplication().getActionManager();
0584         for (Map.Entry<String, Action> e : actionManager.actionsFor(controller).entrySet()) {
0585             String actionName = actionManager.normalizeName(e.getKey());
0586             JavaFXAction action = (JavaFXActione.getValue().getToolkitAction();
0587             actionMatcher.match(node, actionName, action);
0588         }
0589     }
0590 
0591     public static void connectActions(@Nonnull Object node, @Nonnull GriffonController controller) {
0592         connectActions(node, controller, DEFAULT_ACTION_MATCHER);
0593     }
0594 
0595     public static void configureControl(@Nonnull Object control, @Nonnull JavaFXAction action) {
0596         if (control instanceof ButtonBase) {
0597             configure(((ButtonBasecontrol), action);
0598         else if (control instanceof MenuItem) {
0599             JavaFXUtils.configure(((MenuItemcontrol), action);
0600         else if (control instanceof Node) {
0601             ((Nodecontrol).addEventHandler(ActionEvent.ACTION, wrapAction(action));
0602         else {
0603             // does it support the onAction property?
0604             try {
0605                 invokeInstanceMethod(control, "setOnAction", wrapAction(action));
0606             catch (InstanceMethodInvocationException imie) {
0607                 // ignore
0608             }
0609         }
0610     }
0611 
0612     private static EventHandler<ActionEvent> wrapAction(@Nonnull final JavaFXAction action) {
0613         return event -> {
0614             if (action.isEnabled()) {
0615                 action.getOnAction().handle(event);
0616             }
0617         };
0618     }
0619 
0620     private static void runInsideUIThread(@Nonnull Runnable runnable) {
0621         if (Platform.isFxApplicationThread()) {
0622             runnable.run();
0623         else {
0624             Platform.runLater(runnable);
0625         }
0626     }
0627 
0628     public static String normalizeStyle(@Nonnull String style, @Nonnull String key, @Nonnull String value) {
0629         requireNonBlank(style, "Argument 'style' must not be blank");
0630         requireNonBlank(key, "Argument 'key' must not be blank");
0631         requireNonBlank(value, "Argument 'value' must not be blank");
0632 
0633         int start = style.indexOf(key);
0634         if (start != -1) {
0635             int end = style.indexOf(";", start);
0636             end = end >= start ? end : style.length() 1;
0637             style = style.substring(0, start+ style.substring(end + 1);
0638         }
0639         return style + key + ": " + value + ";";
0640     }
0641 
0642     public static void configure(@Nonnull final ToggleButton control, @Nonnull final JavaFXAction action) {
0643         configure((ButtonBasecontrol, action);
0644 
0645         action.selectedProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setSelected(n)));
0646         runInsideUIThread(() -> control.setSelected(action.isSelected()));
0647     }
0648 
0649     public static void configure(@Nonnull final CheckBox control, @Nonnull final JavaFXAction action) {
0650         configure((ButtonBasecontrol, action);
0651 
0652         action.selectedProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setSelected(n)));
0653         runInsideUIThread(() -> control.setSelected(action.isSelected()));
0654     }
0655 
0656     public static void configure(@Nonnull final RadioButton control, @Nonnull final JavaFXAction action) {
0657         configure((ButtonBasecontrol, action);
0658 
0659         action.selectedProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setSelected(n)));
0660         runInsideUIThread(() -> control.setSelected(action.isSelected()));
0661     }
0662 
0663     public static void configure(@Nonnull final ButtonBase control, @Nonnull final JavaFXAction action) {
0664         requireNonNull(control, ERROR_CONTROL_NULL);
0665         requireNonNull(action, ERROR_ACTION_NULL);
0666 
0667         action.onActionProperty().addListener((v, o, n-> control.setOnAction(n));
0668         control.setOnAction(action.getOnAction());
0669 
0670         action.nameProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setText(n)));
0671         runInsideUIThread(() -> control.setText(action.getName()));
0672 
0673         action.descriptionProperty().addListener((v, o, n-> setTooltip(control, n));
0674         setTooltip(control, action.getDescription());
0675 
0676         action.iconProperty().addListener((v, o, n-> setIcon(control, n));
0677         if (!isBlank(action.getIcon())) {
0678             setIcon(control, action.getIcon());
0679         }
0680 
0681         action.imageProperty().addListener((v, o, n-> setGraphic(control, n));
0682         if (null != action.getImage()) {
0683             setGraphic(control, action.getImage());
0684         }
0685 
0686         action.graphicProperty().addListener((v, o, n-> setGraphic(control, n));
0687         if (null != action.getGraphic()) {
0688             setGraphic(control, action.getGraphic());
0689         }
0690 
0691         action.enabledProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setDisable(!n)));
0692         runInsideUIThread(() -> control.setDisable(!action.isEnabled()));
0693 
0694         action.visibleProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setVisible(n)));
0695         runInsideUIThread(() -> control.setVisible(action.isVisible()));
0696 
0697         action.styleClassProperty().addListener((v, o, n-> {
0698             setStyleClass(control, o, true);
0699             setStyleClass(control, n);
0700         });
0701         setStyleClass(control, action.getStyleClass());
0702 
0703         action.styleProperty().addListener((v, o, n-> setStyle(control, n));
0704         setStyle(control, action.getStyle());
0705 
0706         action.graphicStyleClassProperty().addListener((v, o, n-> {
0707             setGraphicStyleClass(control, o, true);
0708             setGraphicStyleClass(control, n);
0709         });
0710         setGraphicStyleClass(control, action.getGraphicStyleClass());
0711 
0712         action.graphicStyleProperty().addListener((v, o, n-> setGraphicStyle(control, n));
0713         setGraphicStyle(control, action.getGraphicStyle());
0714     }
0715 
0716     public static void configure(@Nonnull final CheckMenuItem control, @Nonnull final JavaFXAction action) {
0717         configure((MenuItemcontrol, action);
0718 
0719         action.selectedProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setSelected(n)));
0720         runInsideUIThread(() -> control.setSelected(action.isSelected()));
0721     }
0722 
0723     public static void configure(@Nonnull final RadioMenuItem control, @Nonnull final JavaFXAction action) {
0724         configure((MenuItemcontrol, action);
0725 
0726         action.selectedProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setSelected(n)));
0727         runInsideUIThread(() -> control.setSelected(action.isSelected()));
0728     }
0729 
0730     public static void configure(@Nonnull final MenuItem control, @Nonnull final JavaFXAction action) {
0731         requireNonNull(control, ERROR_CONTROL_NULL);
0732         requireNonNull(action, ERROR_ACTION_NULL);
0733 
0734         action.onActionProperty().addListener((v, o, n-> control.setOnAction(n));
0735         control.setOnAction(action.getOnAction());
0736 
0737         action.nameProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setText(n)));
0738         runInsideUIThread(() -> control.setText(action.getName()));
0739 
0740         action.iconProperty().addListener((v, o, n-> setIcon(control, n));
0741         if (!isBlank(action.getIcon())) {
0742             setIcon(control, action.getIcon());
0743         }
0744 
0745         action.imageProperty().addListener((v, o, n-> setGraphic(control, n));
0746         if (null != action.getImage()) {
0747             setGraphic(control, action.getImage());
0748         }
0749 
0750         action.graphicProperty().addListener((v, o, n-> setGraphic(control, n));
0751         if (null != action.getGraphic()) {
0752             setGraphic(control, action.getGraphic());
0753         }
0754 
0755         action.enabledProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setDisable(!n)));
0756         runInsideUIThread(() -> control.setDisable(!action.getEnabled()));
0757 
0758         action.acceleratorProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setAccelerator(n)));
0759         runInsideUIThread(() -> control.setAccelerator(action.getAccelerator()));
0760 
0761         action.visibleProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setVisible(n)));
0762         runInsideUIThread(() -> control.setVisible(action.isVisible()));
0763 
0764         action.styleClassProperty().addListener((v, o, n-> {
0765             setStyleClass(control, o, true);
0766             setStyleClass(control, n);
0767         });
0768         setStyleClass(control, action.getStyleClass());
0769 
0770         action.styleProperty().addListener((v, o, n-> setStyle(control, n));
0771         setStyle(control, action.getStyle());
0772 
0773         action.graphicStyleClassProperty().addListener((v, o, n-> {
0774             setGraphicStyleClass(control, o, true);
0775             setGraphicStyleClass(control, n);
0776         });
0777         setGraphicStyleClass(control, action.getGraphicStyleClass());
0778 
0779         action.graphicStyleProperty().addListener((v, o, n-> setGraphicStyle(control, n));
0780         setGraphicStyle(control, action.getGraphicStyle());
0781     }
0782 
0783     public static void setStyle(@Nonnull Node node, @Nonnull String style) {
0784         requireNonNull(node, ERROR_CONTROL_NULL);
0785         if (isNull(style)) { return}
0786         if (style.startsWith("&")) {
0787             // append style
0788             String nodeStyle = node.getStyle();
0789             node.setStyle(nodeStyle + (nodeStyle.endsWith(";""" ";"+ style.substring(1));
0790         else {
0791             node.setStyle(style);
0792         }
0793     }
0794 
0795     public static void setStyle(@Nonnull MenuItem node, @Nonnull String style) {
0796         requireNonNull(node, ERROR_CONTROL_NULL);
0797         if (isNull(style)) { return}
0798         if (style.startsWith("&")) {
0799             // append style
0800             String nodeStyle = node.getStyle();
0801             node.setStyle(nodeStyle + (nodeStyle.endsWith(";""" ";"+ style.substring(1));
0802         else {
0803             node.setStyle(style);
0804         }
0805     }
0806 
0807     public static void setGraphicStyle(@Nonnull ButtonBase node, @Nonnull String graphicStyle) {
0808         requireNonNull(node, ERROR_CONTROL_NULL);
0809         if (isNull(graphicStyle)) { return}
0810         if (node.getGraphic() != null) {
0811             setStyle(node.getGraphic(), graphicStyle);
0812         }
0813     }
0814 
0815     public static void setGraphicStyle(@Nonnull MenuItem node, @Nonnull String graphicStyle) {
0816         requireNonNull(node, ERROR_CONTROL_NULL);
0817         if (isNull(graphicStyle)) { return}
0818         if (node.getGraphic() != null) {
0819             setStyle(node.getGraphic(), graphicStyle);
0820         }
0821     }
0822 
0823     public static void setStyleClass(@Nonnull Node node, @Nonnull String styleClass) {
0824         setStyleClass(node, styleClass, false);
0825     }
0826 
0827     public static void setStyleClass(@Nonnull Node node, @Nonnull String styleClass, boolean remove) {
0828         requireNonNull(node, ERROR_CONTROL_NULL);
0829         if (isBlank(styleClass)) { return}
0830 
0831         ObservableList<String> styleClasses = node.getStyleClass();
0832         applyStyleClass(styleClass, styleClasses, remove);
0833     }
0834 
0835     public static void setStyleClass(@Nonnull MenuItem node, @Nonnull String styleClass) {
0836         setStyleClass(node, styleClass, false);
0837     }
0838 
0839     public static void setStyleClass(@Nonnull MenuItem node, @Nonnull String styleClass, boolean remove) {
0840         requireNonNull(node, ERROR_CONTROL_NULL);
0841         if (isBlank(styleClass)) { return}
0842         ObservableList<String> styleClasses = node.getStyleClass();
0843         applyStyleClass(styleClass, styleClasses, remove);
0844     }
0845 
0846     public static void setGraphicStyleClass(@Nonnull ButtonBase node, @Nonnull String graphicStyleClass) {
0847         setGraphicStyleClass(node, graphicStyleClass, false);
0848     }
0849 
0850     public static void setGraphicStyleClass(@Nonnull ButtonBase node, @Nonnull String graphicStyleClass, boolean remove) {
0851         requireNonNull(node, ERROR_CONTROL_NULL);
0852         if (isBlank(graphicStyleClass|| node.getGraphic() == null) { return}
0853 
0854         ObservableList<String> graphicStyleClasses = node.getGraphic().getStyleClass();
0855         applyStyleClass(graphicStyleClass, graphicStyleClasses, remove);
0856     }
0857 
0858     public static void setGraphicStyleClass(@Nonnull MenuItem node, @Nonnull String graphicStyleClass) {
0859         setGraphicStyleClass(node, graphicStyleClass, false);
0860     }
0861 
0862     public static void setGraphicStyleClass(@Nonnull MenuItem node, @Nonnull String graphicStyleClass, boolean remove) {
0863         requireNonNull(node, ERROR_CONTROL_NULL);
0864         if (isBlank(graphicStyleClass|| node.getGraphic() == null) { return}
0865 
0866         ObservableList<String> graphicStyleClasses = node.getGraphic().getStyleClass();
0867         applyStyleClass(graphicStyleClass, graphicStyleClasses, remove);
0868     }
0869 
0870     private static void applyStyleClass(String styleClass, ObservableList<String> styleClasses, boolean remove) {
0871         runInsideUIThread(() -> {
0872             String[] strings = styleClass.split("[,\\ ]");
0873             if (remove) {
0874                 styleClasses.removeAll(strings);
0875             else {
0876                 Set<String> classes = new LinkedHashSet<>(styleClasses);
0877                 for (String s : strings) {
0878                     if (isBlank(s)) { continue}
0879                     classes.add(s.trim());
0880                 }
0881                 styleClasses.setAll(classes);
0882             }
0883         });
0884     }
0885 
0886     public static void setTooltip(@Nonnull Control control, @Nullable String text) {
0887         runInsideUIThread(() -> {
0888             if (isBlank(text)) {
0889                 return;
0890             }
0891             requireNonNull(control, ERROR_CONTROL_NULL);
0892 
0893             Tooltip tooltip = control.tooltipProperty().get();
0894             if (tooltip == null) {
0895                 tooltip = new Tooltip();
0896                 control.tooltipProperty().set(tooltip);
0897             }
0898             tooltip.setText(text);
0899         });
0900     }
0901 
0902     public static void setIcon(@Nonnull Labeled control, @Nonnull String iconUrl) {
0903         requireNonNull(control, ERROR_CONTROL_NULL);
0904         requireNonBlank(iconUrl, ERROR_ICON_BLANK);
0905 
0906         Node graphicNode = resolveIcon(iconUrl);
0907         if (graphicNode != null) {
0908             runInsideUIThread(() -> control.graphicProperty().set(graphicNode));
0909         }
0910     }
0911 
0912     public static void setIcon(@Nonnull MenuItem control, @Nonnull String iconUrl) {
0913         requireNonNull(control, ERROR_CONTROL_NULL);
0914         requireNonBlank(iconUrl, ERROR_ICON_BLANK);
0915 
0916         Node graphicNode = resolveIcon(iconUrl);
0917         if (graphicNode != null) {
0918             runInsideUIThread(() -> control.graphicProperty().set(graphicNode));
0919         }
0920     }
0921 
0922     public static void setGraphic(@Nonnull Labeled control, @Nullable Image graphic) {
0923         requireNonNull(control, ERROR_CONTROL_NULL);
0924 
0925         runInsideUIThread(() -> {
0926             if (graphic != null) {
0927                 Node graphicNode = new ImageView(graphic);
0928                 control.graphicProperty().set(graphicNode);
0929             else {
0930                 control.graphicProperty().set(null);
0931             }
0932         });
0933     }
0934 
0935     public static void setGraphic(@Nonnull MenuItem control, @Nullable Image graphic) {
0936         requireNonNull(control, ERROR_CONTROL_NULL);
0937 
0938         runInsideUIThread(() -> {
0939             if (graphic != null) {
0940                 Node graphicNode = new ImageView(graphic);
0941                 control.graphicProperty().set(graphicNode);
0942             else {
0943                 control.graphicProperty().set(null);
0944             }
0945         });
0946     }
0947 
0948     public static void setGraphic(@Nonnull Labeled control, @Nullable Node graphic) {
0949         requireNonNull(control, ERROR_CONTROL_NULL);
0950 
0951         runInsideUIThread(() -> {
0952             if (graphic != null) {
0953                 control.graphicProperty().set(graphic);
0954             else {
0955                 control.graphicProperty().set(null);
0956             }
0957         });
0958     }
0959 
0960     public static void setGraphic(@Nonnull MenuItem control, @Nullable Node graphic) {
0961         requireNonNull(control, ERROR_CONTROL_NULL);
0962 
0963         runInsideUIThread(() -> {
0964             if (graphic != null) {
0965                 control.graphicProperty().set(graphic);
0966             else {
0967                 control.graphicProperty().set(null);
0968             }
0969         });
0970     }
0971 
0972     @Nullable
0973     public static Node resolveIcon(@Nonnull String iconUrl) {
0974         requireNonBlank(iconUrl, ERROR_URL_BLANK);
0975 
0976         if (iconUrl.contains("|")) {
0977             // assume classname|arg format
0978             return handleAsClassWithArg(iconUrl);
0979         else {
0980             URL resource = Thread.currentThread().getContextClassLoader().getResource(iconUrl);
0981             if (resource != null) {
0982                 return new ImageView(new Image(resource.toString()));
0983             }
0984         }
0985         return null;
0986     }
0987 
0988     @SuppressWarnings("unchecked")
0989     private static Node handleAsClassWithArg(String str) {
0990         String[] args = str.split("\\|");
0991         if (args.length == 2) {
0992             Class<?> iconClass = null;
0993             try {
0994                 iconClass = (Class<?>JavaFXUtils.class.getClassLoader().loadClass(args[0]);
0995             catch (ClassNotFoundException e) {
0996                 throw illegalValue(str, Node.class, e);
0997             }
0998 
0999             Constructor<?> constructor = null;
1000             try {
1001                 constructor = iconClass.getConstructor(String.class);
1002             catch (NoSuchMethodException e) {
1003                 throw illegalValue(str, Node.class, e);
1004             }
1005 
1006             try {
1007                 Object o = constructor.newInstance(args[1]);
1008                 if (instanceof Node) {
1009                     return (Nodeo;
1010                 else if (instanceof Image) {
1011                     return new ImageView((Imageo);
1012                 else {
1013                     throw illegalValue(str, Node.class);
1014                 }
1015             catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
1016                 throw illegalValue(str, Node.class, e);
1017             }
1018         else {
1019             throw illegalValue(str, Node.class);
1020         }
1021     }
1022 
1023     @Nullable
1024     public static Node findNode(@Nonnull Node root, @Nonnull String id) {
1025         requireNonNull(root, ERROR_ROOT_NULL);
1026         requireNonBlank(id, ERROR_ID_BLANK);
1027 
1028         if (id.equals(root.getId())) { return root; }
1029 
1030 
1031         if (root instanceof TabPane) {
1032             TabPane parent = (TabPaneroot;
1033             for (Tab child : parent.getTabs()) {
1034                 if (child.getContent() != null) {
1035                     Node found = findNode(child.getContent(), id);
1036                     if (found != null) { return found; }
1037                 }
1038             }
1039         else if (root instanceof TitledPane) {
1040             TitledPane parent = (TitledPaneroot;
1041             if (parent.getContent() != null) {
1042                 Node found = findNode(parent.getContent(), id);
1043                 if (found != null) { return found; }
1044             }
1045         else if (root instanceof Accordion) {
1046             Accordion parent = (Accordionroot;
1047             for (TitledPane child : parent.getPanes()) {
1048                 Node found = findNode(child, id);
1049                 if (found != null) { return found; }
1050             }
1051         else if (root instanceof SplitPane) {
1052             SplitPane parent = (SplitPaneroot;
1053             for (Node child : parent.getItems()) {
1054                 Node found = findNode(child, id);
1055                 if (found != null) { return found; }
1056             }
1057         else if (root instanceof ScrollPane) {
1058             ScrollPane scrollPane = (ScrollPaneroot;
1059             if (scrollPane.getContent() != null) {
1060                 Node found = findNode(scrollPane.getContent(), id);
1061                 if (found != null) { return found; }
1062             }
1063         else if (root instanceof ToolBar) {
1064             ToolBar toolBar = (ToolBarroot;
1065             for (Node child : toolBar.getItems()) {
1066                 Node found = findNode(child, id);
1067                 if (found != null) { return found; }
1068             }
1069         else if (root instanceof ButtonBar) {
1070             ButtonBar buttonBar = (ButtonBarroot;
1071             for (Node child : buttonBar.getButtons()) {
1072                 Node found = findNode(child, id);
1073                 if (found != null) { return found; }
1074             }
1075         else if (root instanceof Parent) {
1076             Parent parent = (Parentroot;
1077             for (Node child : parent.getChildrenUnmodifiable()) {
1078                 Node found = findNode(child, id);
1079                 if (found != null) { return found; }
1080             }
1081         }
1082 
1083         return null;
1084     }
1085 
1086     @Nullable
1087     public static Object findElement(@Nonnull Object root, @Nonnull String id) {
1088         requireNonNull(root, ERROR_ROOT_NULL);
1089         requireNonBlank(id, ERROR_ID_BLANK);
1090 
1091         if (id.equals(getPropertyValue(root, "id"))) { return root; }
1092 
1093         if (root instanceof Control) {
1094             Control control = (Controlroot;
1095             ContextMenu contextMenu = control.getContextMenu();
1096             if (contextMenu != null) {
1097                 Object found = findElement(contextMenu, id);
1098                 if (found != null) {return found;}
1099             }
1100             Tooltip tooltip = control.getTooltip();
1101             if (tooltip != null) {
1102                 Object found = findElement(tooltip, id);
1103                 if (found != null) {return found;}
1104             }
1105         }
1106 
1107         if (root instanceof ButtonBar) {
1108             ButtonBar buttonBar = (ButtonBarroot;
1109             for (Node child : buttonBar.getButtons()) {
1110                 Object found = findElement(child, id);
1111                 if (found != null) { return found; }
1112             }
1113         else if (root instanceof MenuBar) {
1114             MenuBar menuBar = (MenuBarroot;
1115             for (Menu child : menuBar.getMenus()) {
1116                 Object found = findElement(child, id);
1117                 if (found != null) { return found; }
1118             }
1119         else if (root instanceof ContextMenu) {
1120             ContextMenu contextMenu = (ContextMenuroot;
1121             for (MenuItem child : contextMenu.getItems()) {
1122                 Object found = findElement(child, id);
1123                 if (found != null) { return found; }
1124             }
1125         else if (root instanceof Menu) {
1126             Menu menu = (Menuroot;
1127             for (MenuItem child : menu.getItems()) {
1128                 Object found = findElement(child, id);
1129                 if (found != null) { return found; }
1130             }
1131         else if (root instanceof TabPane) {
1132             TabPane tabPane = (TabPaneroot;
1133             for (Tab child : tabPane.getTabs()) {
1134                 Object found = findElement(child, id);
1135                 if (found != null) { return found; }
1136             }
1137         else if (root instanceof Tab) {
1138             Tab tab = (Tabroot;
1139             if (tab.getContent() != null) {
1140                 Object found = findElement(tab.getContent(), id);
1141                 if (found != null) { return found; }
1142             }
1143         else if (root instanceof TitledPane) {
1144             TitledPane parent = (TitledPaneroot;
1145             if (parent.getContent() != null) {
1146                 Object found = findElement(parent.getContent(), id);
1147                 if (found != null) { return found; }
1148             }
1149         else if (root instanceof Accordion) {
1150             Accordion parent = (Accordionroot;
1151             for (TitledPane child : parent.getPanes()) {
1152                 Object found = findElement(child, id);
1153                 if (found != null) { return found; }
1154             }
1155         else if (root instanceof SplitPane) {
1156             SplitPane parent = (SplitPaneroot;
1157             for (Node child : parent.getItems()) {
1158                 Object found = findElement(child, id);
1159                 if (found != null) { return found; }
1160             }
1161         else if (root instanceof ScrollPane) {
1162             ScrollPane scrollPane = (ScrollPaneroot;
1163             if (scrollPane.getContent() != null) {
1164                 Object found = findElement(scrollPane.getContent(), id);
1165                 if (found != null) { return found; }
1166             }
1167         else if (root instanceof ToolBar) {
1168             ToolBar toolBar = (ToolBarroot;
1169             for (Node child : toolBar.getItems()) {
1170                 Object found = findElement(child, id);
1171                 if (found != null) { return found; }
1172             }
1173         else if (root instanceof Parent) {
1174             Parent parent = (Parentroot;
1175             for (Node child : parent.getChildrenUnmodifiable()) {
1176                 Object found = findElement(child, id);
1177                 if (found != null) { return found; }
1178             }
1179         }
1180 
1181         return null;
1182     }
1183 
1184     @Nullable
1185     public static Object findElement(@Nonnull Object root, @Nonnull Predicate<Object> predicate) {
1186         requireNonNull(root, ERROR_ROOT_NULL);
1187         requireNonNull(predicate, ERROR_PREDICATE_NULL);
1188 
1189         if (predicate.test(root)) {
1190             return root;
1191         }
1192 
1193         if (root instanceof Control) {
1194             Control control = (Controlroot;
1195             ContextMenu contextMenu = control.getContextMenu();
1196             if (contextMenu != null) {
1197                 Object found = findElement(contextMenu, predicate);
1198                 if (found != null) {return found;}
1199             }
1200             Tooltip tooltip = control.getTooltip();
1201             if (tooltip != null) {
1202                 Object found = findElement(tooltip, predicate);
1203                 if (found != null) {return found;}
1204             }
1205         }
1206 
1207         if (root instanceof ButtonBar) {
1208             ButtonBar buttonBar = (ButtonBarroot;
1209             for (Node child : buttonBar.getButtons()) {
1210                 Object found = findElement(child, predicate);
1211                 if (found != null) { return found; }
1212             }
1213         else if (root instanceof MenuBar) {
1214             MenuBar menuBar = (MenuBarroot;
1215             for (Menu child : menuBar.getMenus()) {
1216                 Object found = findElement(child, predicate);
1217                 if (found != null) { return found; }
1218             }
1219         else if (root instanceof ContextMenu) {
1220             ContextMenu contextMenu = (ContextMenuroot;
1221             for (MenuItem child : contextMenu.getItems()) {
1222                 Object found = findElement(child, predicate);
1223                 if (found != null) { return found; }
1224             }
1225         else if (root instanceof Menu) {
1226             Menu menu = (Menuroot;
1227             for (MenuItem child : menu.getItems()) {
1228                 Object found = findElement(child, predicate);
1229                 if (found != null) { return found; }
1230             }
1231         else if (root instanceof TabPane) {
1232             TabPane tabPane = (TabPaneroot;
1233             for (Tab child : tabPane.getTabs()) {
1234                 Object found = findElement(child, predicate);
1235                 if (found != null) { return found; }
1236             }
1237         else if (root instanceof Tab) {
1238             Tab tab = (Tabroot;
1239             if (tab.getContent() != null) {
1240                 Object found = findElement(tab.getContent(), predicate);
1241                 if (found != null) { return found; }
1242             }
1243         else if (root instanceof TitledPane) {
1244             TitledPane parent = (TitledPaneroot;
1245             if (parent.getContent() != null) {
1246                 Object found = findElement(parent.getContent(), predicate);
1247                 if (found != null) { return found; }
1248             }
1249         else if (root instanceof Accordion) {
1250             Accordion parent = (Accordionroot;
1251             for (TitledPane child : parent.getPanes()) {
1252                 Object found = findElement(child, predicate);
1253                 if (found != null) { return found; }
1254             }
1255         else if (root instanceof SplitPane) {
1256             SplitPane parent = (SplitPaneroot;
1257             for (Node child : parent.getItems()) {
1258                 Object found = findElement(child, predicate);
1259                 if (found != null) { return found; }
1260             }
1261         else if (root instanceof ScrollPane) {
1262             ScrollPane scrollPane = (ScrollPaneroot;
1263             if (scrollPane.getContent() != null) {
1264                 Object found = findElement(scrollPane.getContent(), predicate);
1265                 if (found != null) { return found; }
1266             }
1267         else if (root instanceof ToolBar) {
1268             ToolBar toolBar = (ToolBarroot;
1269             for (Node child : toolBar.getItems()) {
1270                 Object found = findElement(child, predicate);
1271                 if (found != null) { return found; }
1272             }
1273         else if (root instanceof Parent) {
1274             Parent parent = (Parentroot;
1275             for (Node child : parent.getChildrenUnmodifiable()) {
1276                 Object found = findElement(child, predicate);
1277                 if (found != null) { return found; }
1278             }
1279         }
1280 
1281         return null;
1282     }
1283 
1284     @Nonnull
1285     public static Collection<Object> findElements(@Nonnull Object root, @Nonnull Predicate<Object> predicate) {
1286         Set<Object> accumulator = new LinkedHashSet<>();
1287         findElements(root, predicate, accumulator);
1288         return accumulator;
1289     }
1290 
1291     private static void findElements(@Nonnull Object root, @Nonnull Predicate<Object> predicate, @Nonnull Collection<Object> accumulator) {
1292         requireNonNull(root, ERROR_ROOT_NULL);
1293         requireNonNull(predicate, ERROR_PREDICATE_NULL);
1294 
1295         if (predicate.test(root)) {
1296             accumulator.add(root);
1297         }
1298 
1299         if (root instanceof Control) {
1300             Control control = (Controlroot;
1301             ContextMenu contextMenu = control.getContextMenu();
1302             if (contextMenu != null) {
1303                 findElements(contextMenu, predicate, accumulator);
1304             }
1305             Tooltip tooltip = control.getTooltip();
1306             if (tooltip != null) {
1307                 findElements(tooltip, predicate, accumulator);
1308             }
1309         }
1310 
1311         if (root instanceof ButtonBar) {
1312             ButtonBar buttonBar = (ButtonBarroot;
1313             for (Node child : buttonBar.getButtons()) {
1314                 findElements(child, predicate, accumulator);
1315             }
1316         else if (root instanceof MenuBar) {
1317             MenuBar menuBar = (MenuBarroot;
1318             for (Menu child : menuBar.getMenus()) {
1319                 findElements(child, predicate, accumulator);
1320             }
1321         else if (root instanceof ContextMenu) {
1322             ContextMenu contextMenu = (ContextMenuroot;
1323             for (MenuItem child : contextMenu.getItems()) {
1324                 findElements(child, predicate, accumulator);
1325             }
1326         else if (root instanceof Menu) {
1327             Menu menu = (Menuroot;
1328             for (MenuItem child : menu.getItems()) {
1329                 findElements(child, predicate, accumulator);
1330             }
1331         else if (root instanceof TabPane) {
1332             TabPane tabPane = (TabPaneroot;
1333             for (Tab child : tabPane.getTabs()) {
1334                 findElements(child, predicate, accumulator);
1335             }
1336         else if (root instanceof Tab) {
1337             Tab tab = (Tabroot;
1338             if (tab.getContent() != null) {
1339                 findElements(tab.getContent(), predicate, accumulator);
1340             }
1341         else if (root instanceof TitledPane) {
1342             TitledPane parent = (TitledPaneroot;
1343             if (parent.getContent() != null) {
1344                 findElements(parent.getContent(), predicate, accumulator);
1345             }
1346         else if (root instanceof Accordion) {
1347             Accordion parent = (Accordionroot;
1348             for (TitledPane child : parent.getPanes()) {
1349                 findElements(child, predicate, accumulator);
1350             }
1351         else if (root instanceof SplitPane) {
1352             SplitPane parent = (SplitPaneroot;
1353             for (Node child : parent.getItems()) {
1354                 findElements(child, predicate, accumulator);
1355             }
1356         else if (root instanceof ScrollPane) {
1357             ScrollPane scrollPane = (ScrollPaneroot;
1358             if (scrollPane.getContent() != null) {
1359                 findElements(scrollPane.getContent(), predicate, accumulator);
1360             }
1361         else if (root instanceof ToolBar) {
1362             ToolBar toolBar = (ToolBarroot;
1363             for (Node child : toolBar.getItems()) {
1364                 findElements(child, predicate, accumulator);
1365             }
1366         else if (root instanceof Parent) {
1367             Parent parent = (Parentroot;
1368             for (Node child : parent.getChildrenUnmodifiable()) {
1369                 findElements(child, predicate, accumulator);
1370             }
1371         }
1372     }
1373 
1374     @Nullable
1375     public static Window getWindowAncestor(@Nonnull Object node) {
1376         requireNonNull(node, ERROR_NODE_NULL);
1377 
1378         if (node instanceof Window) {
1379             return (Windownode;
1380         else if (node instanceof Scene) {
1381             return ((Scenenode).getWindow();
1382         else if (node instanceof Node) {
1383             Scene scene = ((Nodenode).getScene();
1384             if (scene != null) {
1385                 return scene.getWindow();
1386             }
1387         else if (node instanceof Tab) {
1388             TabPane tabPane = ((Tabnode).getTabPane();
1389             if (tabPane != null) {
1390                 return getWindowAncestor(tabPane);
1391             }
1392         }
1393 
1394         return null;
1395     }
1396 
1397     private static ValueConversionException illegalValue(Object value, Class<?> klass) {
1398         throw new ValueConversionException(value, klass);
1399     }
1400 
1401     private static ValueConversionException illegalValue(Object value, Class<?> klass, Exception e) {
1402         throw new ValueConversionException(value, klass, e);
1403     }
1404 }