Regex look-behind sin longitud máxima obvia en Java

Siempre pensé que una afirmación de mirar hacia atrás en la API regex de Java (y muchos otros lenguajes para el caso) debe tener una longitud obvia. Entonces, los cuantificadores STAR y PLUS no están permitidos dentro de las miradas .

El excelente recurso en línea regular-expressions.info parece confirmar (algunas de) mis suposiciones:

“[…] Java lleva las cosas un paso más allá al permitir la repetición finita. Todavía no puedes usar la estrella o más, pero puedes usar el signo de interrogación y las llaves con el parámetro máximo especificado. Java reconoce el hecho de que la repetición finita puede reescribirse como una alternancia de cadenas con longitudes diferentes, pero fijas. Desafortunadamente, el JDK 1.4 y el 1.5 tienen algunos errores cuando usa alternancia dentro de lookbehind. Estos fueron arreglados en JDK 1.6. […] ”

– http://www.regular-expressions.info/lookaround.html

El uso de llaves funciona siempre que la longitud total del rango de los caracteres dentro de la mira sea menor o igual que Integer.MAX_VALUE. Entonces estas expresiones regulares son válidas:

"(?<=a{0," +(Integer.MAX_VALUE) + "})B" "(?<=Ca{0," +(Integer.MAX_VALUE-1) + "})B" "(?<=CCa{0," +(Integer.MAX_VALUE-2) + "})B" 

Pero estos no son:

 "(?<=Ca{0," +(Integer.MAX_VALUE) +"})B" "(?<=CCa{0," +(Integer.MAX_VALUE-1) +"})B" 

Sin embargo, no entiendo lo siguiente:

Cuando realizo una prueba usando el cuantificador * y + dentro de un ” look-behind” , todo va bien (vea la prueba de salida 1 y la prueba 2 ).

Pero, cuando agrego un solo carácter al comienzo de la mirada atrás de la Prueba 1 y la Prueba 2 , se rompe (vea la Prueba de salida 3 ).

Hacer que los codiciosos * de la Prueba 3 sean reacios no tiene ningún efecto, aún se rompe (ver Prueba 4 ).

Aquí está el arnés de prueba:

 public class Main { private static String testFind(String regex, String input) { try { boolean returned = java.util.regex.Pattern.compile(regex).matcher(input).find(); return "testFind : Valid -> regex = "+regex+", input = "+input+", returned = "+returned; } catch(Exception e) { return "testFind : Invalid -> "+regex+", "+e.getMessage(); } } private static String testReplaceAll(String regex, String input) { try { String returned = input.replaceAll(regex, "FOO"); return "testReplaceAll : Valid -> regex = "+regex+", input = "+input+", returned = "+returned; } catch(Exception e) { return "testReplaceAll : Invalid -> "+regex+", "+e.getMessage(); } } private static String testSplit(String regex, String input) { try { String[] returned = input.split(regex); return "testSplit : Valid -> regex = "+regex+", input = "+input+", returned = "+java.util.Arrays.toString(returned); } catch(Exception e) { return "testSplit : Invalid -> "+regex+", "+e.getMessage(); } } public static void main(String[] args) { String[] regexes = {"(?<=a*)B", "(?<=a+)B", "(?<=Ca*)B", "(?<=Ca*?)B"}; String input = "CaaaaaaaaaaaaaaaBaaaa"; int test = 0; for(String regex : regexes) { test++; System.out.println("********************** Test "+test+" **********************"); System.out.println(" "+testFind(regex, input)); System.out.println(" "+testReplaceAll(regex, input)); System.out.println(" "+testSplit(regex, input)); System.out.println(); } } } 

La salida:

 ********************** Test 1 ********************** testFind : Valid -> regex = (? regex = (? regex = (? regex = (? regex = (? regex = (? (?<=Ca*)B, Look-behind group does not have an obvious maximum length near index 6 (? (?<=Ca*)B, Look-behind group does not have an obvious maximum length near index 6 (? (?<=Ca*)B, Look-behind group does not have an obvious maximum length near index 6 (? (?<=Ca*?)B, Look-behind group does not have an obvious maximum length near index 7 (? (?<=Ca*?)B, Look-behind group does not have an obvious maximum length near index 7 (? (?<=Ca*?)B, Look-behind group does not have an obvious maximum length near index 7 (?<=Ca*?)B ^ 

Mi pregunta puede ser obvia, pero aún así la preguntaré: ¿alguien me puede explicar por qué las pruebas 1 y 2 fallan y las pruebas 3 y 4 no? Hubiera esperado que todos fallaran, ni la mitad de ellos trabajaran y la mitad de ellos fracasaran.

Gracias.

PD. Estoy usando: versión de Java 1.6.0_14

Al echar un vistazo al código fuente de Pattern.java, se revela que ‘*’ y ‘+’ se implementan como instancias de Curly (que es el objeto creado para operadores rizados). Asi que,

 a* 

se implementa como

 a{0,0x7FFFFFFF} 

y

 a+ 

se implementa como

 a{1,0x7FFFFFFF} 

por eso es que ves exactamente los mismos comportamientos para los cráneos y las estrellas.

Es un error: http://bugs.sun.com/view_bug.do?bug_id=6695369

Siempre se supone que Pattern.compile() lanza una excepción si no puede determinar la longitud máxima posible de una coincidencia lookbehind.