1

I want a few JTextField within my program to only accept new input if it matches an specific Pattern.

I know about JFormattedTextField's masks and that is not what I want here.

For each new input, the text should only accept the changes if those comply with the flow of an specific regex pattern.

This has to be done with regex because of "regex-demanding" situations, like if the input should receive parts of itself. This is better understood with an example:

Suppose my regex is "(\\d{2})-bk\\d{3}\\.\\1". With an input of "123-bk001.", the next inputted characters must be equal to the first group, meaning the 3 inputted characters should only be accepted if they equal 123, respectively. An input of "456-bk404." would require the next chars to be "456" instead.

How can I do it?


Edit:

This example is just to illustrate one of the situations that is hard to solve without regex, and my actual uses would not be limited to it. So, if possible, answers should be of the broader usability (as of the question's tittle), rather than specific to this example.

But if it's not possible or if it would be harder than implementing specific solutions for the few (3-5) different cases, feel free to tell.

CosmicGiant
  • 6,275
  • 5
  • 43
  • 58
  • You can check regular expressions in a java `Format` which can be used with a `JFormattedTextField`. You find an example of the `JFormattedTextField` [here](http://stackoverflow.com/a/13424140/1076463) – Robin Nov 28 '12 at 17:25
  • @Robin - Can you provide a link? I can't seem to find anything related to regex within `Format`'s docs. – CosmicGiant Nov 29 '12 at 14:24
  • You can write your own `Format` in which you do validation based on a regular expression. I am not aware of any `Format` which provides this out of the box – Robin Nov 29 '12 at 15:26
  • @Robin - Where would the validation be applied within the `Format`'s structure? I'm guessing it's withing the `.format()` method, but I'm unsure. --- And how do I integrate the `Format` with the `TextComponent` afterwards so as to block invalid input as it happens? – CosmicGiant Nov 29 '12 at 15:30
  • 1
    @Robin (me guessing :-) probably meant that you have to implement a custom Format/ter which uses the regex (core has no support for it, afair) for parsing and allowing/rejecting input. My first look would be into DefaultFormatter: it has Navigation- and DocumentFilters installed which you might be able to tweak (never tried, though) – kleopatra Nov 29 '12 at 15:31
  • @kleopatra that was indeed what I was suggesting – Robin Nov 29 '12 at 15:36
  • also, you probably want to think about usability: f.i. why allow (or even force) the user to input the last block if it is entirely defined by the first block? Would expect your formatter to fill the parts that I can get only wrong :-) Might not be trivial to implement but might be possible in a not too general context. – kleopatra Nov 29 '12 at 15:41
  • That is just an example...My situations differ slightly from it, but achieving a solution for the example will be sufficient to implement the real thing. --- I'm still confused as to just how would I implement the suggested custom class for the intended purpose. – CosmicGiant Nov 29 '12 at 16:28

2 Answers2

2
Community
  • 1
  • 1
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • As the example shows, the *defined of unwanted chars* in this case are dynamic. The same regex should accept `111-bk001.111` and `222-bk002.222`. But not accept the last 3 digits if they differ from the first 3 (or whatever group was specifid in the regex). – CosmicGiant Nov 28 '12 at 17:37
  • see [Regex](http://stackoverflow.com/questions/7318622/regex-determine-matrix-from-string) and [working example, idea valid for JTextComponents too](http://stackoverflow.com/a/6421385/714968) – mKorbel Nov 28 '12 at 17:48
  • Sorry, either I'm not understanding how to apply this approach, or this does not work. --- I can set it up for simpler things, such as allowing positive digits only, by overriding `insertString()` and `replace()` to apply a `replaceAll("\\D++")` to the `string` and `text` parameters. --- But when it comes to my intended purpose, or any composed pattern really, using a `Matcher` to match what I want and exclude everything else is not working as intended. – CosmicGiant Nov 29 '12 at 14:39
  • I somewhat understand the use of the `DocumentFilter`, but I don't understand how to make it accept the matching of the regex pattern before the pattern is complete. --- For example, `"123-"` "matches" part of the pattern, but not it's whole. I'd need to do something like a `(currentText + newInput).matchesPatternPartially()==true` check, but I don't know how to get `currentText` from the document the filter has been attached to, nor how to make a `matchesPatternPartially()` kind of function. – CosmicGiant Nov 30 '12 at 18:28
  • 1
    @TheLima [please edit question with code that you tried :-)](http://stackoverflow.com/a/10848530/714968), – mKorbel Nov 30 '12 at 21:53
0

My gut approach is to use the DocumentFilter and modify the Regex you're testing against based on how long your test string is. So if your test string is 10 characters long, the regex you'd use to test it is "(\\d{2})-bk\\d{3}\\." or "\\d\\d\\d-bk\\d\\d\\d\\d\\.". This would pass "123-bk0001.", but fail "123-bk000a.".

It would take some customization for each regex you would want to process (for example placing the parenthesis in the correct spot in the regex based on the length of the test string), but I don't think there's a way to make a regex dynamic based on length (which is what you're after).

import javax.swing.*;
import javax.swing.text.*;

public class JTextFieldSuperVerified extends Box{

    public JTextFieldSuperVerified() {
        super(BoxLayout.Y_AXIS);

        final JTextField textBox = new JTextField(20);

        ((AbstractDocument)textBox.getDocument()).setDocumentFilter(new DocumentFilter(){

            public void insertString(DocumentFilter.FilterBypass fb,int offset,String string,AttributeSet attr) throws BadLocationException{
                StringBuilder newString = new StringBuilder(textBox.getText());
                //Recreate the insert for testing
                newString.insert(offset, string);
                if(verifyText(newString.toString())){
                    fb.insertString(offset, string, attr);
                }

            }

            public void remove(DocumentFilter.FilterBypass fb, int offset, int length) throws BadLocationException{
                StringBuilder newString = new StringBuilder(textBox.getText());
                //Recreate the delete for testing
                newString.delete(offset, offset + length);
                if(verifyText(newString.toString())){
                    fb.remove(offset, length);
                }
            }


            public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException{
                StringBuilder newString = new StringBuilder(textBox.getText());
                //Recreate the replace for testing
                newString.replace(offset, offset + length, text);
                if(verifyText(newString.toString())){
                    fb.replace(offset, length, text, attrs);
                }
            }

            //make sure the change is allowed
            public boolean verifyText(String s){
                boolean result = true;
                //Our basic regex to test
                StringBuilder regexPattern = new StringBuilder("\\d\\d\\d-bk\\d\\d\\d\\d\\.\\1");


                if(s.length() < 15){
                    //How we modify the regex based on how long the string we're testing is
                    if(s.length() < 4)
                        regexPattern.delete(s.length() * 2, regexPattern.length());
                    else if(s.length() < 7)
                        regexPattern.delete(s.length() + 3, regexPattern.length());
                    else if(s.length() < 12)
                        regexPattern.delete((s.length() - 3) * 2 + 3, regexPattern.length());
                    else if(s.length() < 15){
                        regexPattern.insert((s.length() - 11) * 2, ')');
                        regexPattern.insert(0, '(');
                    }
                    System.out.println(regexPattern.toString());
                    result = s.matches(regexPattern.toString());

                }else{
                    //Fail everything over 14 chars
                    result = false;
                }

                return result;
            }

        });

        add(textBox);

    }

    public static void main(String[] args){
        final JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new JTextFieldSuperVerified());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}
Nick Rippe
  • 6,465
  • 14
  • 30