How about this:
String adjacent =
"fooaXbXdbar".replaceAll(".*?(.)X(?:(?=(.)X)|(.).*?(?=.X|$))", "$1$2$3");
?
What it does is, after the X
, it first checks to see if it's immediately followed by .X
, in which case it captures the .
as $2
and considers the match complete; if it finds that it's not immediately followed by .X
, it goes on to use the same logic that you were already using, capturing the subsequent character as $3
.
(Note: I've tested this with both of your examples, but obviously it may miss other cases that you need to support. I recommend that you test it yourself as well.)