JavaFXUtils.java
001 /*
002  * Copyright 2008-2016 the original author or authors.
003  *
004  * Licensed under the Apache License, Version 2.0 (the "License");
005  * you may not use this file except in compliance with the License.
006  * You may obtain a copy of the License at
007  *
008  *     http://www.apache.org/licenses/LICENSE-2.0
009  *
010  * Unless required by applicable law or agreed to in writing, software
011  * distributed under the License is distributed on an "AS IS" BASIS,
012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013  * See the License for the specific language governing permissions and
014  * limitations under the License.
015  */
016 package griffon.javafx.support;
017 
018 import griffon.core.artifact.GriffonController;
019 import griffon.core.controller.Action;
020 import griffon.core.controller.ActionManager;
021 import griffon.core.editors.ValueConversionException;
022 import griffon.exceptions.InstanceMethodInvocationException;
023 import javafx.application.Platform;
024 import javafx.beans.property.Property;
025 import javafx.collections.ListChangeListener;
026 import javafx.collections.ObservableList;
027 import javafx.event.ActionEvent;
028 import javafx.scene.Node;
029 import javafx.scene.Parent;
030 import javafx.scene.Scene;
031 import javafx.scene.control.Accordion;
032 import javafx.scene.control.ButtonBase;
033 import javafx.scene.control.CheckBox;
034 import javafx.scene.control.CheckMenuItem;
035 import javafx.scene.control.ContextMenu;
036 import javafx.scene.control.Control;
037 import javafx.scene.control.Labeled;
038 import javafx.scene.control.Menu;
039 import javafx.scene.control.MenuBar;
040 import javafx.scene.control.MenuItem;
041 import javafx.scene.control.RadioButton;
042 import javafx.scene.control.RadioMenuItem;
043 import javafx.scene.control.ScrollPane;
044 import javafx.scene.control.SplitPane;
045 import javafx.scene.control.Tab;
046 import javafx.scene.control.TabPane;
047 import javafx.scene.control.TitledPane;
048 import javafx.scene.control.ToggleButton;
049 import javafx.scene.control.ToolBar;
050 import javafx.scene.control.Tooltip;
051 import javafx.scene.image.Image;
052 import javafx.scene.image.ImageView;
053 import javafx.stage.Window;
054 
055 import javax.annotation.Nonnull;
056 import javax.annotation.Nullable;
057 import java.lang.reflect.Constructor;
058 import java.lang.reflect.InvocationTargetException;
059 import java.net.URL;
060 import java.util.LinkedHashSet;
061 import java.util.Map;
062 import java.util.Set;
063 
064 import static griffon.util.GriffonClassUtils.getGetterName;
065 import static griffon.util.GriffonClassUtils.getPropertyValue;
066 import static griffon.util.GriffonClassUtils.invokeExactInstanceMethod;
067 import static griffon.util.GriffonClassUtils.invokeInstanceMethod;
068 import static griffon.util.GriffonNameUtils.isBlank;
069 import static griffon.util.GriffonNameUtils.requireNonBlank;
070 import static java.util.Objects.requireNonNull;
071 
072 /**
073  @author Andres Almiray
074  */
075 public final class JavaFXUtils {
076     private static final String ERROR_NODE_NULL = "Argument 'node' must not be null";
077     private static final String ERROR_CONTROL_NULL = "Argument 'control' must not be null";
078     private static final String ERROR_ACTION_NULL = "Argument 'action' must not be null";
079     private static final String ERROR_ICON_BLANK = "Argument 'iconUrl' must not be blank";
080     private static final String ERROR_ID_BLANK = "Argument 'id' must not be blank";
081     private static final String ERROR_URL_BLANK = "Argument 'url' must not be blank";
082     private static final String ERROR_ROOT_NULL = "Argument 'root' must not be null";
083     private static final String ERROR_CONTROLLER_NULL = "Argument 'controller' must not be null";
084     private static final String ACTION_TARGET_SUFFIX = "ActionTarget";
085     private static final String PROPERTY_SUFFIX = "Property";
086 
087     private JavaFXUtils() {
088 
089     }
090 
091     /**
092      * Wraps an <tt>ObservableList</tt>, publishing updates inside the UI thread.
093      *
094      @param source the <tt>ObservableList</tt> to be wrapped
095      @param <E>    the list's paramter type.
096      @return a new  <tt>ObservableList</tt>
097      @since 2.6.0
098      */
099     @Nonnull
100     public static <E> ObservableList<E> createJavaFXThreadProxyList(@Nonnull ObservableList<E> source) {
101         requireNonNull(source, "Argument 'source' must not be null");
102         return new JavaFXThreadProxyObservableList<>(source);
103     }
104 
105     private static class JavaFXThreadProxyObservableList<E> extends DelegatingObservableList<E> {
106         protected JavaFXThreadProxyObservableList(ObservableList<E> delegate) {
107             super(delegate);
108         }
109 
110         @Override
111         protected void sourceChanged(@Nonnull final ListChangeListener.Change<? extends E> c) {
112             if (Platform.isFxApplicationThread()) {
113                 fireChange(c);
114             else {
115                 Platform.runLater(() -> fireChange(c));
116             }
117         }
118     }
119 
120     @Nonnull
121     @SuppressWarnings("ConstantConditions")
122     public static <B> Property<?> extractProperty(@Nonnull B bean, @Nonnull String propertyName) {
123         requireNonNull(bean, "Argument 'bean' must not be null");
124         requireNonBlank(propertyName, "Argument 'propertyName' must not be null");
125 
126         if (!propertyName.endsWith(PROPERTY_SUFFIX)) {
127             propertyName += PROPERTY_SUFFIX;
128         }
129 
130         InstanceMethodInvocationException imie;
131         try {
132             // 1. try <columnName>Property() first
133             return (Property<?>invokeExactInstanceMethod(bean, propertyName);
134         catch (InstanceMethodInvocationException e) {
135             imie = e;
136         }
137 
138         // 2. fallback to get<columnName>Property()
139         try {
140             return (Property<?>invokeExactInstanceMethod(bean, getGetterName(propertyName));
141         catch (InstanceMethodInvocationException e) {
142             throw imie;
143         }
144     }
145 
146     public static void connectActions(@Nonnull Object node, @Nonnull GriffonController controller) {
147         requireNonNull(node, ERROR_NODE_NULL);
148         requireNonNull(controller, ERROR_CONTROLLER_NULL);
149         ActionManager actionManager = controller.getApplication().getActionManager();
150         for (Map.Entry<String, Action> e : actionManager.actionsFor(controller).entrySet()) {
151             String actionTargetName = actionManager.normalizeName(e.getKey()) + ACTION_TARGET_SUFFIX;
152             Object control = findElement(node, actionTargetName);
153             if (control == nullcontinue;
154             JavaFXAction action = (JavaFXActione.getValue().getToolkitAction();
155 
156             if (control instanceof ButtonBase) {
157                 configure(((ButtonBasecontrol), action);
158             else if (control instanceof MenuItem) {
159                 JavaFXUtils.configure(((MenuItemcontrol), action);
160             else if (control instanceof Node) {
161                 ((Nodecontrol).addEventHandler(ActionEvent.ACTION, action.getOnAction());
162             else {
163                 // does it support the onAction property?
164                 try {
165                     invokeInstanceMethod(control, "setOnAction", action.getOnAction());
166                 catch (InstanceMethodInvocationException imie) {
167                     // ignore
168                 }
169             }
170         }
171     }
172 
173     private static void runInsideUIThread(@Nonnull Runnable runnable) {
174         if (Platform.isFxApplicationThread()) {
175             runnable.run();
176         else {
177             Platform.runLater(runnable);
178         }
179     }
180 
181     public static void configure(final @Nonnull ToggleButton control, final @Nonnull JavaFXAction action) {
182         configure((ButtonBasecontrol, action);
183 
184         action.selectedProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setSelected(n)));
185         runInsideUIThread(() -> control.setSelected(action.isSelected()));
186     }
187 
188     public static void configure(final @Nonnull CheckBox control, final @Nonnull JavaFXAction action) {
189         configure((ButtonBasecontrol, action);
190 
191         action.selectedProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setSelected(n)));
192         runInsideUIThread(() -> control.setSelected(action.isSelected()));
193     }
194 
195     public static void configure(final @Nonnull RadioButton control, final @Nonnull JavaFXAction action) {
196         configure((ButtonBasecontrol, action);
197 
198         action.selectedProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setSelected(n)));
199         runInsideUIThread(() -> control.setSelected(action.isSelected()));
200     }
201 
202     public static void configure(final @Nonnull ButtonBase control, final @Nonnull JavaFXAction action) {
203         requireNonNull(control, ERROR_CONTROL_NULL);
204         requireNonNull(action, ERROR_ACTION_NULL);
205 
206         action.onActionProperty().addListener((v, o, n-> control.setOnAction(n));
207         control.setOnAction(action.getOnAction());
208 
209         action.nameProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setText(n)));
210         runInsideUIThread(() -> control.setText(action.getName()));
211 
212         action.descriptionProperty().addListener((v, o, n-> setTooltip(control, n));
213         setTooltip(control, action.getDescription());
214 
215         action.iconProperty().addListener((v, o, n-> setIcon(control, n));
216         if (!isBlank(action.getIcon())) {
217             setIcon(control, action.getIcon());
218         }
219 
220         action.imageProperty().addListener((v, o, n-> setGraphic(control, n));
221         if (null != action.getImage()) {
222             setGraphic(control, action.getImage());
223         }
224 
225         action.graphicProperty().addListener((v, o, n-> setGraphic(control, n));
226         if (null != action.getGraphic()) {
227             setGraphic(control, action.getGraphic());
228         }
229 
230         action.enabledProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setDisable(!n)));
231         runInsideUIThread(() -> control.setDisable(!action.isEnabled()));
232 
233         action.visibleProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setVisible(n)));
234         runInsideUIThread(() -> control.setVisible(action.isVisible()));
235 
236         action.styleClassProperty().addListener((v, o, n-> {
237             setStyleClass(control, o, true);
238             setStyleClass(control, n);
239         });
240         setStyleClass(control, action.getStyleClass());
241     }
242 
243     public static void configure(final @Nonnull CheckMenuItem control, final @Nonnull JavaFXAction action) {
244         configure((MenuItemcontrol, action);
245 
246         action.selectedProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setSelected(n)));
247         runInsideUIThread(() -> control.setSelected(action.isSelected()));
248     }
249 
250     public static void configure(final @Nonnull RadioMenuItem control, final @Nonnull JavaFXAction action) {
251         configure((MenuItemcontrol, action);
252 
253         action.selectedProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setSelected(n)));
254         runInsideUIThread(() -> control.setSelected(action.isSelected()));
255     }
256 
257     public static void configure(final @Nonnull MenuItem control, final @Nonnull JavaFXAction action) {
258         requireNonNull(control, ERROR_CONTROL_NULL);
259         requireNonNull(action, ERROR_ACTION_NULL);
260 
261         action.onActionProperty().addListener((v, o, n-> control.setOnAction(n));
262         control.setOnAction(action.getOnAction());
263 
264         action.nameProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setText(n)));
265         runInsideUIThread(() -> control.setText(action.getName()));
266 
267         action.iconProperty().addListener((v, o, n-> setIcon(control, n));
268         if (!isBlank(action.getIcon())) {
269             setIcon(control, action.getIcon());
270         }
271 
272         action.imageProperty().addListener((v, o, n-> setGraphic(control, n));
273         if (null != action.getImage()) {
274             setGraphic(control, action.getImage());
275         }
276 
277         action.graphicProperty().addListener((v, o, n-> setGraphic(control, n));
278         if (null != action.getGraphic()) {
279             setGraphic(control, action.getGraphic());
280         }
281 
282         action.enabledProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setDisable(!n)));
283         runInsideUIThread(() -> control.setDisable(!action.getEnabled()));
284 
285         action.acceleratorProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setAccelerator(n)));
286         runInsideUIThread(() -> control.setAccelerator(action.getAccelerator()));
287 
288         action.visibleProperty().addListener((v, o, n-> runInsideUIThread(() -> control.setVisible(n)));
289         runInsideUIThread(() -> control.setVisible(action.isVisible()));
290 
291         action.styleClassProperty().addListener((v, o, n-> {
292             setStyleClass(control, o, true);
293             setStyleClass(control, n);
294         });
295         setStyleClass(control, action.getStyleClass());
296     }
297 
298     public static void setStyleClass(@Nonnull Node node, @Nonnull String styleClass) {
299         setStyleClass(node, styleClass, false);
300     }
301 
302     public static void setStyleClass(@Nonnull Node node, @Nonnull String styleClass, boolean remove) {
303         requireNonNull(node, ERROR_CONTROL_NULL);
304         if (isBlank(styleClass)) return;
305 
306         ObservableList<String> styleClasses = node.getStyleClass();
307         applyStyleClass(styleClass, styleClasses, remove);
308     }
309 
310     public static void setStyleClass(@Nonnull MenuItem node, @Nonnull String styleClass) {
311         setStyleClass(node, styleClass, false);
312     }
313 
314     public static void setStyleClass(@Nonnull MenuItem node, @Nonnull String styleClass, boolean remove) {
315         requireNonNull(node, ERROR_CONTROL_NULL);
316         if (isBlank(styleClass)) return;
317         ObservableList<String> styleClasses = node.getStyleClass();
318         applyStyleClass(styleClass, styleClasses, remove);
319     }
320 
321     private static void applyStyleClass(String styleClass, ObservableList<String> styleClasses, boolean remove) {
322         runInsideUIThread(() -> {
323             String[] strings = styleClass.split("[,\\ ]");
324             if (remove) {
325                 styleClasses.removeAll(strings);
326             else {
327                 Set<String> classes = new LinkedHashSet<>(styleClasses);
328                 for (String s : strings) {
329                     if (isBlank(s)) continue;
330                     classes.add(s.trim());
331                 }
332                 styleClasses.setAll(classes);
333             }
334         });
335     }
336 
337     public static void setTooltip(@Nonnull Control control, @Nullable String text) {
338         runInsideUIThread(() -> {
339             if (isBlank(text)) {
340                 return;
341             }
342             requireNonNull(control, ERROR_CONTROL_NULL);
343 
344             Tooltip tooltip = control.tooltipProperty().get();
345             if (tooltip == null) {
346                 tooltip = new Tooltip();
347                 control.tooltipProperty().set(tooltip);
348             }
349             tooltip.setText(text);
350         });
351     }
352 
353     public static void setIcon(@Nonnull Labeled control, @Nonnull String iconUrl) {
354         requireNonNull(control, ERROR_CONTROL_NULL);
355         requireNonBlank(iconUrl, ERROR_ICON_BLANK);
356 
357         Node graphicNode = resolveIcon(iconUrl);
358         if (graphicNode != null) {
359             runInsideUIThread(() -> control.graphicProperty().set(graphicNode));
360         }
361     }
362 
363     public static void setIcon(@Nonnull MenuItem control, @Nonnull String iconUrl) {
364         requireNonNull(control, ERROR_CONTROL_NULL);
365         requireNonBlank(iconUrl, ERROR_ICON_BLANK);
366 
367         Node graphicNode = resolveIcon(iconUrl);
368         if (graphicNode != null) {
369             runInsideUIThread(() -> control.graphicProperty().set(graphicNode));
370         }
371     }
372 
373     public static void setGraphic(@Nonnull Labeled control, @Nullable Image graphic) {
374         requireNonNull(control, ERROR_CONTROL_NULL);
375 
376         runInsideUIThread(() -> {
377             if (graphic != null) {
378                 Node graphicNode = new ImageView(graphic);
379                 control.graphicProperty().set(graphicNode);
380             else {
381                 control.graphicProperty().set(null);
382             }
383         });
384     }
385 
386     public static void setGraphic(@Nonnull MenuItem control, @Nullable Image graphic) {
387         requireNonNull(control, ERROR_CONTROL_NULL);
388 
389         runInsideUIThread(() -> {
390             if (graphic != null) {
391                 Node graphicNode = new ImageView(graphic);
392                 control.graphicProperty().set(graphicNode);
393             else {
394                 control.graphicProperty().set(null);
395             }
396         });
397     }
398 
399     public static void setGraphic(@Nonnull Labeled control, @Nullable Node graphic) {
400         requireNonNull(control, ERROR_CONTROL_NULL);
401 
402         runInsideUIThread(() -> {
403             if (graphic != null) {
404                 control.graphicProperty().set(graphic);
405             else {
406                 control.graphicProperty().set(null);
407             }
408         });
409     }
410 
411     public static void setGraphic(@Nonnull MenuItem control, @Nullable Node graphic) {
412         requireNonNull(control, ERROR_CONTROL_NULL);
413 
414         runInsideUIThread(() -> {
415             if (graphic != null) {
416                 control.graphicProperty().set(graphic);
417             else {
418                 control.graphicProperty().set(null);
419             }
420         });
421     }
422 
423     @Nullable
424     public static Node resolveIcon(@Nonnull String iconUrl) {
425         requireNonBlank(iconUrl, ERROR_URL_BLANK);
426 
427         if (iconUrl.contains("|")) {
428             // assume classname|arg format
429             return handleAsClassWithArg(iconUrl);
430         else {
431             URL resource = Thread.currentThread().getContextClassLoader().getResource(iconUrl);
432             if (resource != null) {
433                 return new ImageView(new Image(resource.toString()));
434             }
435         }
436         return null;
437     }
438 
439     @SuppressWarnings("unchecked")
440     private static Node handleAsClassWithArg(String str) {
441         String[] args = str.split("\\|");
442         if (args.length == 2) {
443             Class<?> iconClass = null;
444             try {
445                 iconClass = (Class<?>JavaFXUtils.class.getClassLoader().loadClass(args[0]);
446             catch (ClassNotFoundException e) {
447                 throw illegalValue(str, Node.class, e);
448             }
449 
450             Constructor<?> constructor = null;
451             try {
452                 constructor = iconClass.getConstructor(String.class);
453             catch (NoSuchMethodException e) {
454                 throw illegalValue(str, Node.class, e);
455             }
456 
457             try {
458                 Object o = constructor.newInstance(args[1]);
459                 if (instanceof Node) {
460                     return (Nodeo;
461                 else if (instanceof Image) {
462                     return new ImageView((Imageo);
463                 else {
464                     throw illegalValue(str, Node.class);
465                 }
466             catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
467                 throw illegalValue(str, Node.class, e);
468             }
469         else {
470             throw illegalValue(str, Node.class);
471         }
472     }
473 
474     @Nullable
475     public static Node findNode(@Nonnull Node root, @Nonnull String id) {
476         requireNonNull(root, ERROR_ROOT_NULL);
477         requireNonBlank(id, ERROR_ID_BLANK);
478 
479         if (id.equals(root.getId())) return root;
480 
481         if (root instanceof TabPane) {
482             TabPane parent = (TabPaneroot;
483             for (Tab child : parent.getTabs()) {
484                 if (child.getContent() != null) {
485                     Node found = findNode(child.getContent(), id);
486                     if (found != nullreturn found;
487                 }
488             }
489         else if (root instanceof TitledPane) {
490             TitledPane parent = (TitledPaneroot;
491             if (parent.getContent() != null) {
492                 Node found = findNode(parent.getContent(), id);
493                 if (found != nullreturn found;
494             }
495         else if (root instanceof Accordion) {
496             Accordion parent = (Accordionroot;
497             for (TitledPane child : parent.getPanes()) {
498                 Node found = findNode(child, id);
499                 if (found != nullreturn found;
500             }
501         else if (root instanceof SplitPane) {
502             SplitPane parent = (SplitPaneroot;
503             for (Node child : parent.getItems()) {
504                 Node found = findNode(child, id);
505                 if (found != nullreturn found;
506             }
507         else if (root instanceof ScrollPane) {
508             ScrollPane scrollPane = (ScrollPaneroot;
509             if (scrollPane.getContent() != null) {
510                 Node found = findNode(scrollPane.getContent(), id);
511                 if (found != nullreturn found;
512             }
513         else if (root instanceof ToolBar) {
514             ToolBar toolBar = (ToolBarroot;
515             for (Node child : toolBar.getItems()) {
516                 Node found = findNode(child, id);
517                 if (found != nullreturn found;
518             }
519         else if (root instanceof Parent) {
520             Parent parent = (Parentroot;
521             for (Node child : parent.getChildrenUnmodifiable()) {
522                 Node found = findNode(child, id);
523                 if (found != nullreturn found;
524             }
525         }
526 
527         return null;
528     }
529 
530     @Nullable
531     public static Object findElement(@Nonnull Object root, @Nonnull String id) {
532         requireNonNull(root, ERROR_ROOT_NULL);
533         requireNonBlank(id, ERROR_ID_BLANK);
534 
535         if (id.equals(getPropertyValue(root, "id"))) return root;
536 
537         if (root instanceof MenuBar) {
538             MenuBar menuBar = (MenuBarroot;
539             for (Menu child : menuBar.getMenus()) {
540                 Object found = findElement(child, id);
541                 if (found != nullreturn found;
542             }
543         }
544         if (root instanceof ContextMenu) {
545             ContextMenu contextMenu = (ContextMenuroot;
546             for (MenuItem child : contextMenu.getItems()) {
547                 Object found = findElement(child, id);
548                 if (found != nullreturn found;
549             }
550         else if (root instanceof Menu) {
551             Menu menu = (Menuroot;
552             for (MenuItem child : menu.getItems()) {
553                 Object found = findElement(child, id);
554                 if (found != nullreturn found;
555             }
556         else if (root instanceof TabPane) {
557             TabPane tabPane = (TabPaneroot;
558             for (Tab child : tabPane.getTabs()) {
559                 Object found = findElement(child, id);
560                 if (found != nullreturn found;
561             }
562         else if (root instanceof Tab) {
563             Tab tab = (Tabroot;
564             if (tab.getContent() != null) {
565                 Object found = findElement(tab.getContent(), id);
566                 if (found != nullreturn found;
567             }
568         else if (root instanceof TitledPane) {
569             TitledPane parent = (TitledPaneroot;
570             if (parent.getContent() != null) {
571                 Object found = findElement(parent.getContent(), id);
572                 if (found != nullreturn found;
573             }
574         else if (root instanceof Accordion) {
575             Accordion parent = (Accordionroot;
576             for (TitledPane child : parent.getPanes()) {
577                 Object found = findElement(child, id);
578                 if (found != nullreturn found;
579             }
580         else if (root instanceof SplitPane) {
581             SplitPane parent = (SplitPaneroot;
582             for (Node child : parent.getItems()) {
583                 Object found = findElement(child, id);
584                 if (found != nullreturn found;
585             }
586         else if (root instanceof ScrollPane) {
587             ScrollPane scrollPane = (ScrollPaneroot;
588             if (scrollPane.getContent() != null) {
589                 Object found = findElement(scrollPane.getContent(), id);
590                 if (found != nullreturn found;
591             }
592         else if (root instanceof ToolBar) {
593             ToolBar toolBar = (ToolBarroot;
594             for (Node child : toolBar.getItems()) {
595                 Node found = findNode(child, id);
596                 if (found != nullreturn found;
597             }
598         else if (root instanceof Parent) {
599             Parent parent = (Parentroot;
600             for (Node child : parent.getChildrenUnmodifiable()) {
601                 Object found = findElement(child, id);
602                 if (found != nullreturn found;
603             }
604         }
605 
606         return null;
607     }
608 
609     @Nullable
610     public static Window getWindowAncestor(@Nonnull Object node) {
611         requireNonNull(node, ERROR_NODE_NULL);
612 
613         if (node instanceof Window) {
614             return (Windownode;
615         else if (node instanceof Scene) {
616             return ((Scenenode).getWindow();
617         else if (node instanceof Node) {
618             Scene scene = ((Nodenode).getScene();
619             if (scene != null) {
620                 return scene.getWindow();
621             }
622         else if (node instanceof Tab) {
623             TabPane tabPane = ((Tabnode).getTabPane();
624             if (tabPane != null) {
625                 return getWindowAncestor(tabPane);
626             }
627         }
628 
629         return null;
630     }
631 
632     private static ValueConversionException illegalValue(Object value, Class<?> klass) {
633         throw new ValueConversionException(value, klass);
634     }
635 
636     private static ValueConversionException illegalValue(Object value, Class<?> klass, Exception e) {
637         throw new ValueConversionException(value, klass, e);
638     }
639 }