2

I would like to make sure the user enter a valid IPv4 address in a JavaFX TextField (0.0.0.0 to 255.255.255.255) but i can't seems to find any valid solutions.

I tried with that code :

    private final TextField serverURI = new TextField();
    final UnaryOperator<TextFormatter.Change> urlFilter = new UnaryOperator<TextFormatter.Change>() {
        @Override
        public TextFormatter.Change apply(TextFormatter.Change change) {
            final String text = change.getText();
            return (text.isEmpty() || text.matches("^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")) ? change : null;
        }
    };
    final TextFormatter<String> urlFormatter = new TextFormatter(urlFilter);
    serverURI.setTextFormatter(urlFormatter);

But I can't even type one digit... It seems to work with only one input

Nivetha T
  • 481
  • 1
  • 3
  • 17
Phoste
  • 1,141
  • 4
  • 18
  • 34

2 Answers2

3

These are tricky.

The first note is that you are using change.getText(), which gives the text being added or removed; you want to test the resulting text (i.e. the text after adding or deleting). For this, use change.getControlNewText().

For example, if the current text in the text field is "255.2", and the user types a "5", then change.getText() will return "5", whereas change.getControlNewText() will return "255.25".

This still leaves the main issue, which is that the filter is being applied to every individual change to the text, and you are testing for a complete ip address. So, for example, to type "255.255.255.255", the user would first type "2". You test (even with change.getControlNewText()) if "2" matches your regular expression, it fails (because it's not a complete IP address), and so the change is vetoed. On the next key typed, change.getControlNewText() will be "25", and so on. So you want the entire sequence "2", "25", "255", "255.", "255.2", etc, etc to pass your filter. You also need the filter to accept the user deleting characters, copying and pasting, and so on.

So you really need a regex that tests for partial entries, instead of complete entries, which is of course a little trickier to get right. The following is an example, not intended to be bullet-proof, but should get you on the right track (at least). Note that you probably want additional validation when the user commits the value, to check you have a complete, valid ip address.

import java.util.function.UnaryOperator;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class IPTextFieldTest extends Application {

    @Override
    public void start(Stage primaryStage) {
        TextField ipTextField = new TextField();
        String regex = makePartialIPRegex();
        final UnaryOperator<Change> ipAddressFilter = c -> {
            String text = c.getControlNewText();
            if  (text.matches(regex)) {
                return c ;
            } else {
                return null ;
            }
        };
        ipTextField.setTextFormatter(new TextFormatter<>(ipAddressFilter));
        StackPane root = new StackPane(ipTextField);
        Scene scene = new Scene(root, 350, 120);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private String makePartialIPRegex() {
        String partialBlock = "(([01]?[0-9]{0,2})|(2[0-4][0-9])|(25[0-5]))" ;
        String subsequentPartialBlock = "(\\."+partialBlock+")" ;
        String ipAddress = partialBlock+"?"+subsequentPartialBlock+"{0,3}";
        return "^"+ipAddress ;
    }


    public static void main(String[] args) {
        launch(args);
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thanks, it works fine ! But I don't understand why it works with your regex and not mine even if I use getControlNewText() instead of getText() I just have to figure out how to make sure the user enter something between two dots and maybe prevent to put zero in front :) – Phoste Aug 19 '15 at 12:56
  • Because `"2"`, `"25"`, `"255"`, `"255."`, `"255.2"`, etc etc do not match your regex. It is checking every time the text changes. – James_D Aug 19 '15 at 13:02
  • Edited answer to clarify that last point. Please accept the answer as correct if it answers your question. – James_D Aug 19 '15 at 13:12
  • One last note: I would not try to avoid leading zeros. For example, if a user had accidentally typed `200` instead of `100`, and you did not allow `00` in any block, it would make it unreasonably hard for them to edit it. – James_D Aug 19 '15 at 13:15
  • It will return true if you enter only a dot. – Polar Jan 11 '18 at 11:17
0

You can manually check the numbers.

private static boolean checkIfValidIpv4(String text){
   StringTokenizer st = new StringTokenizer(text,".");
   for(int i = 0; i < 4; i++){ 
     if(!st.hasMoreTokens()){
       return false;
     }
     int num = Integer.parseInt(st.nextToken());
     if(num < 0 || num > 255){
       return false;
    }
   }
   if(st.hasMoreTokens()){
     return false;
   }
   return true;
}

It's not elegant but it works.

Tawcharowsky
  • 615
  • 4
  • 18
  • Yes but it doesn't prevent the user to type anything but digits. I would like something in real time :) – Phoste Aug 19 '15 at 11:56
  • oh... if that's the case, I can't help you :) But you can check [this](http://stackoverflow.com/questions/22040845/mask-the-ip-address-when-the-user-enters-in-text-field-java-swing) question... it's sort of similar. @Phoste – Tawcharowsky Aug 19 '15 at 12:34