This ought to work:
(?![_-])(?!(?:.*[_-]){2,})[A-Za-z_-]{1,14}
The regex is quite complex, let my try and explain it.
(?![_-])
negative lookahead. From the start of the string assert that the first character is not _
or -
. The negative lookahead "peeks" of the current position and checks that it doesn't match [_-]
which is a character group containing _
and -
.
(?!(?:.*[_-]){2,})
another negative lookahead, this time matching (?:.*[_-]){2,}
which is a non capturing group repeated at least two times. The group is .*[_-]
, it is any character followed by the same group as before. So we don't want to see some characters followed by _
or -
more than once.
[A-Za-z_-]{1,14}
is the simple bit. It just says the characters in the group [A-Za-z_-]
between 1 and 14 times.
The second part of the pattern is the most tricky, but is a very common trick. If you want to see a character A
repeated at some point in the pattern at least X
times you want to see the pattern .*A
at least X
times because you must have
zzzzAzzzzAzzzzA....
You don't care what else is there. So what you arrive at is (.*A){X,}
. Now, you don't need to capture the group - this just slows down the engine. So we make the group non-capturing - (?:.*A){X,}
.
What you have is that you only want to see the pattern once, so you want not to find the pattern repeated two or more times. Hence it slots into a negative lookahead.
Here is a testcase:
public static void main(String[] args) {
final String pattern = "(?![_-])(?!(?:.*[_-]){2,})[A-Za-z_-]{1,14}";
final String[] tests = {
"Hello-Again",
"ThisIsValid",
"AlsoThis_",
"_NotThis_",
"-notvalid",
"Not-Allow-This",
"Nor-This_thing",
"VeryVeryLongStringIndeed",
};
for (final String test : tests) {
System.out.println(test.matches(pattern));
}
}
Output:
true
true
true
false
false
false
false
false
Things to note:
- the character
-
is special inside character groups. It must go at the start or end of a group otherwise it specifies a range
- lookaround is tricky and often counter-intuitive. It will check for matches without consuming, allowing you to test multiple conditions on the same data.
- the repetition quantifier
{}
is very useful. It has 3 states. {X}
is repeated exactly X
times. {X,}
is repeated at least X
times. And {X, Y}
is repeated between X
and Y
times.