-
Bug
-
Resolution: Incomplete
-
P4
-
None
-
8u40
-
JavaFX 8u40 64bit, JDK 8u40 64bit, Windows 7 64bit, NetBeans 8.0.2
Apparently any control that inherits from TextInputControl has an issue when it has a TextFormatter that contains a StringConverter which has a toString() method that returns null (for example when the input value is null).
This raises a NullPointerException in the method private <T> void updateText(TextFormatter<T> formatter) in the class TexInputControl because the value returned from converter.toString() is not checked before calling replaceText(). As of now the code for this method is as follows in TextInputControl:
private <T> void updateText(TextFormatter<T> formatter) {
T value = formatter.getValue();
StringConverter<T> converter = formatter.getValueConverter();
if (converter != null) {
String text = converter.toString(value);
replaceText(0, getLength(), text, text.length(), text.length());
}
}
The documentation from StringConverter.toString() does not specify if null is an illegal value or not.
A way to circumvent the issue is to return the empty string ("") instead of null.
The issue arose to me while doing a TextFormatter for a spinner that allows people to enter a port value. The value of the spinner is initially null in order to let the user see the prompt text.
Value factory for the Spinner:
public final class PortValueFactory extends SpinnerValueFactory<Integer> {
@Override
public void decrement(int steps) {
final Integer value = getValue();
final int nextValue = (value == null) ? PortTextFormatter.MIN_PORT : Math.max(value - 1, PortTextFormatter.MIN_PORT);
setValue(nextValue);
}
@Override
public void increment(int steps) {
final Integer value = getValue();
final int nextValue = (value == null) ? PortTextFormatter.MIN_PORT : Math.min(value + 1, PortTextFormatter.MAX_PORT);
setValue(nextValue);
}
}
Textformatter for the Spinner's editor:
public final class PortTextFormatter extends TextFormatter<Integer> {
/**
* The min port value.
*/
public static final int MIN_PORT = 0;
/**
* The max port value.
*/
public static final int MAX_PORT = 2 * Short.MAX_VALUE + 1;
/**
* Creates a new instance.
*/
public PortTextFormatter() {
// super(Converter.getInstance(), null, Filter.getInstance());
super(Converter.getInstance(), null, null);
}
/**
* A filter that only accept short values that are valid for a port.
* @author Fabrice Bouyé (fabriceb@spc.int)
*/
public final static class Filter implements UnaryOperator<TextFormatter.Change> {
/**
* The unique instance of this class.
*/
private static Filter INSTANCE;
/**
* Hidden constructor.
*/
private Filter() {
}
@Override
public TextFormatter.Change apply(TextFormatter.Change change) {
final String text = change.getText();
TextFormatter.Change result = (text.isEmpty() || text.matches("[0-9]+")) ? change : null; // NOI18N.
try {
final int value = Integer.parseInt(change.getControlNewText());
if (value < MIN_PORT || value > MAX_PORT) {
result = null;
}
} catch (NumberFormatException ex) {
// MOViTLogger.LOGGER.log(Level.FINEST, ex.getMessage(), ex);
}
return result;
}
/**
* Gets the unique instance of this class.
* @return The unique instance of this class.
*/
public synchronized static Filter getInstance() {
if (INSTANCE == null) {
INSTANCE = new Filter();
}
return INSTANCE;
}
}
/**
* A converter that only accept short values that are valid for a port.
* @author Fabrice Bouyé (fabriceb@spc.int)
*/
public final static class Converter extends StringConverter<Integer> {
/**
* The unique instance of this class.
*/
private static Converter INSTANCE;
/**
* Hidden constructor.
*/
private Converter() {
}
@Override
public String toString(final Integer value) {
final Integer val = (value == null) ? null : Math.max(MIN_PORT, Math.min(MAX_PORT, value));
// return (val == null) ? "" : val.toString();
return (val == null) ? null : val.toString();
}
@Override
public Integer fromString(final String text) {
Integer result = (text == null || text.trim().isEmpty()) ? null : Integer.parseInt(text.trim());
result = (result == null) ? MIN_PORT : Math.max(MIN_PORT, Math.min(MAX_PORT, result));
return result;
}
/**
* Gets the unique instance of this class.
* @return The unique instance of this class.
*/
public synchronized static Converter getInstance() {
if (INSTANCE == null) {
INSTANCE = new Converter();
}
return INSTANCE;
}
}
}
Test code:
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
Spinner<Integer> spinner = new Spinner();
spinner.setValueFactory(new PortValueFactory());
spinner.getEditor().setTextFormatter(new PortTextFormatter());
StackPane root = new StackPane();
root.getChildren().add(spinner);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Test");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Upon launch the following exception is raised:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$152(LauncherImpl.java:182)
at com.sun.javafx.application.LauncherImpl$$Lambda$50/1642360923.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException
at javafx.scene.control.TextInputControl.updateText(TextInputControl.java:1215)
at javafx.scene.control.TextInputControl.access$300(TextInputControl.java:79)
at javafx.scene.control.TextInputControl$5.invalidated(TextInputControl.java:320)
at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:111)
at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
at javafx.scene.control.TextInputControl.setTextFormatter(TextInputControl.java:334)
at test.Main.start(Main.java:20)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$159(LauncherImpl.java:863)
at com.sun.javafx.application.LauncherImpl$$Lambda$53/1379051502.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$172(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl$$Lambda$45/355629945.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$null$170(PlatformImpl.java:295)
at com.sun.javafx.application.PlatformImpl$$Lambda$48/1675761123.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$171(PlatformImpl.java:294)
at com.sun.javafx.application.PlatformImpl$$Lambda$47/1915503092.run(Unknown Source)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$145(WinApplication.java:101)
at com.sun.glass.ui.win.WinApplication$$Lambda$36/1963387170.run(Unknown Source)
... 1 more
Exception running application test.Main
This can be fixed by returning "" instead of null in the toString() method of the StringConverter wihtin the TextFormatter.
This raises a NullPointerException in the method private <T> void updateText(TextFormatter<T> formatter) in the class TexInputControl because the value returned from converter.toString() is not checked before calling replaceText(). As of now the code for this method is as follows in TextInputControl:
private <T> void updateText(TextFormatter<T> formatter) {
T value = formatter.getValue();
StringConverter<T> converter = formatter.getValueConverter();
if (converter != null) {
String text = converter.toString(value);
replaceText(0, getLength(), text, text.length(), text.length());
}
}
The documentation from StringConverter.toString() does not specify if null is an illegal value or not.
A way to circumvent the issue is to return the empty string ("") instead of null.
The issue arose to me while doing a TextFormatter for a spinner that allows people to enter a port value. The value of the spinner is initially null in order to let the user see the prompt text.
Value factory for the Spinner:
public final class PortValueFactory extends SpinnerValueFactory<Integer> {
@Override
public void decrement(int steps) {
final Integer value = getValue();
final int nextValue = (value == null) ? PortTextFormatter.MIN_PORT : Math.max(value - 1, PortTextFormatter.MIN_PORT);
setValue(nextValue);
}
@Override
public void increment(int steps) {
final Integer value = getValue();
final int nextValue = (value == null) ? PortTextFormatter.MIN_PORT : Math.min(value + 1, PortTextFormatter.MAX_PORT);
setValue(nextValue);
}
}
Textformatter for the Spinner's editor:
public final class PortTextFormatter extends TextFormatter<Integer> {
/**
* The min port value.
*/
public static final int MIN_PORT = 0;
/**
* The max port value.
*/
public static final int MAX_PORT = 2 * Short.MAX_VALUE + 1;
/**
* Creates a new instance.
*/
public PortTextFormatter() {
// super(Converter.getInstance(), null, Filter.getInstance());
super(Converter.getInstance(), null, null);
}
/**
* A filter that only accept short values that are valid for a port.
* @author Fabrice Bouyé (fabriceb@spc.int)
*/
public final static class Filter implements UnaryOperator<TextFormatter.Change> {
/**
* The unique instance of this class.
*/
private static Filter INSTANCE;
/**
* Hidden constructor.
*/
private Filter() {
}
@Override
public TextFormatter.Change apply(TextFormatter.Change change) {
final String text = change.getText();
TextFormatter.Change result = (text.isEmpty() || text.matches("[0-9]+")) ? change : null; // NOI18N.
try {
final int value = Integer.parseInt(change.getControlNewText());
if (value < MIN_PORT || value > MAX_PORT) {
result = null;
}
} catch (NumberFormatException ex) {
// MOViTLogger.LOGGER.log(Level.FINEST, ex.getMessage(), ex);
}
return result;
}
/**
* Gets the unique instance of this class.
* @return The unique instance of this class.
*/
public synchronized static Filter getInstance() {
if (INSTANCE == null) {
INSTANCE = new Filter();
}
return INSTANCE;
}
}
/**
* A converter that only accept short values that are valid for a port.
* @author Fabrice Bouyé (fabriceb@spc.int)
*/
public final static class Converter extends StringConverter<Integer> {
/**
* The unique instance of this class.
*/
private static Converter INSTANCE;
/**
* Hidden constructor.
*/
private Converter() {
}
@Override
public String toString(final Integer value) {
final Integer val = (value == null) ? null : Math.max(MIN_PORT, Math.min(MAX_PORT, value));
// return (val == null) ? "" : val.toString();
return (val == null) ? null : val.toString();
}
@Override
public Integer fromString(final String text) {
Integer result = (text == null || text.trim().isEmpty()) ? null : Integer.parseInt(text.trim());
result = (result == null) ? MIN_PORT : Math.max(MIN_PORT, Math.min(MAX_PORT, result));
return result;
}
/**
* Gets the unique instance of this class.
* @return The unique instance of this class.
*/
public synchronized static Converter getInstance() {
if (INSTANCE == null) {
INSTANCE = new Converter();
}
return INSTANCE;
}
}
}
Test code:
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
Spinner<Integer> spinner = new Spinner();
spinner.setValueFactory(new PortValueFactory());
spinner.getEditor().setTextFormatter(new PortTextFormatter());
StackPane root = new StackPane();
root.getChildren().add(spinner);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Test");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Upon launch the following exception is raised:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
Caused by: java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$152(LauncherImpl.java:182)
at com.sun.javafx.application.LauncherImpl$$Lambda$50/1642360923.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException
at javafx.scene.control.TextInputControl.updateText(TextInputControl.java:1215)
at javafx.scene.control.TextInputControl.access$300(TextInputControl.java:79)
at javafx.scene.control.TextInputControl$5.invalidated(TextInputControl.java:320)
at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:111)
at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
at javafx.scene.control.TextInputControl.setTextFormatter(TextInputControl.java:334)
at test.Main.start(Main.java:20)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$159(LauncherImpl.java:863)
at com.sun.javafx.application.LauncherImpl$$Lambda$53/1379051502.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$172(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl$$Lambda$45/355629945.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$null$170(PlatformImpl.java:295)
at com.sun.javafx.application.PlatformImpl$$Lambda$48/1675761123.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$171(PlatformImpl.java:294)
at com.sun.javafx.application.PlatformImpl$$Lambda$47/1915503092.run(Unknown Source)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$145(WinApplication.java:101)
at com.sun.glass.ui.win.WinApplication$$Lambda$36/1963387170.run(Unknown Source)
... 1 more
Exception running application test.Main
This can be fixed by returning "" instead of null in the toString() method of the StringConverter wihtin the TextFormatter.