2009-10-08 8 views
24

J'ai toujours pensé qu'une assertion look-behind dans l'API regex de Java (et beaucoup d'autres langues d'ailleurs) doit avoir une longueur évidente. Ainsi, les quantificateurs STAR et PLUS ne sont pas autorisés à l'intérieur look-behinds.Regex look-behind sans longueur maximale évidente en Java

L'excellente ressource en ligne regular-expressions.info semble confirmer (certaines) mes hypothèses:

« [...] Java prend les choses un peu plus loin en permettant la répétition finie Vous encore ne pouvez pas utiliser l'étoile. ou plus, mais vous pouvez utiliser le point d'interrogation et les accolades avec le paramètre max spécifié. Java reconnaît le fait que la répétition finie peut être réécrite comme une alternance de chaînes avec différentes, mais des longueurs fixes. Malheureusement, le JDK 1.4 et 1.5 ont quelques bugs lorsque vous utilisez l'alternance à l'intérieur lookbehind. Ces ont été corrigés dans JDK 1.6. [...] »

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

En utilisant les accolades fonctionne aussi longtemps que la longueur totale de la gamme des caractères à l'intérieur du regard-behind est inférieure ou égale à Integer.MAX_VALUE donc. ces expressions rationnelles sont valables:

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

Mais ce ne sont pas:

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

Cependant, je ne comprends pas e e suivant:

Quand je lance un test en utilisant * et + quantificateurs dans un tout va bien regarder en arrière, (voir la sortie Test 1 et Test 2).

Mais, quand j'ajoute un caractère unique au début de l'regarder en arrière de Test 1 et Test 2, il brise (voir la sortie Test 3).

Faire les gourmands * de Test 3 réticents n'a pas d'effet, il se casse encore (voir Test 4).

Voici le harnais de test:

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 sortie:

********************** Test 1 ********************** 
    testFind  : Valid -> regex = (?<=a*)B, input = CaaaaaaaaaaaaaaaBaaaa, returned = true 
    testReplaceAll : Valid -> regex = (?<=a*)B, input = CaaaaaaaaaaaaaaaBaaaa, returned = CaaaaaaaaaaaaaaaFOOaaaa 
    testSplit  : Valid -> regex = (?<=a*)B, input = CaaaaaaaaaaaaaaaBaaaa, returned = [Caaaaaaaaaaaaaaa, aaaa] 

********************** Test 2 ********************** 
    testFind  : Valid -> regex = (?<=a+)B, input = CaaaaaaaaaaaaaaaBaaaa, returned = true 
    testReplaceAll : Valid -> regex = (?<=a+)B, input = CaaaaaaaaaaaaaaaBaaaa, returned = CaaaaaaaaaaaaaaaFOOaaaa 
    testSplit  : Valid -> regex = (?<=a+)B, input = CaaaaaaaaaaaaaaaBaaaa, returned = [Caaaaaaaaaaaaaaa, aaaa] 

********************** Test 3 ********************** 
    testFind  : Invalid -> (?<=Ca*)B, Look-behind group does not have an obvious maximum length near index 6 
(?<=Ca*)B 
    ^
    testReplaceAll : Invalid -> (?<=Ca*)B, Look-behind group does not have an obvious maximum length near index 6 
(?<=Ca*)B 
    ^
    testSplit  : Invalid -> (?<=Ca*)B, Look-behind group does not have an obvious maximum length near index 6 
(?<=Ca*)B 
    ^

********************** Test 4 ********************** 
    testFind  : Invalid -> (?<=Ca*?)B, Look-behind group does not have an obvious maximum length near index 7 
(?<=Ca*?)B 
    ^
    testReplaceAll : Invalid -> (?<=Ca*?)B, Look-behind group does not have an obvious maximum length near index 7 
(?<=Ca*?)B 
    ^
    testSplit  : Invalid -> (?<=Ca*?)B, Look-behind group does not have an obvious maximum length near index 7 
(?<=Ca*?)B 
    ^

Ma question est peut-être évident, mais je vais demander encore: Quelqu'un peut-il me expliquer pourquoi Test 1 et échec, et Test 3 et ne pas? Je m'attendais à tous les échecs, pas la moitié d'entre eux au travail et la moitié d'entre eux pour échouer.

Merci.

PS. J'utilise: Java version 1.6.0_14

Répondre

17

Regarder le code source de Pattern.java révèle que le '*' et le '+' sont implémentés comme des instances de Curly (qui est l'objet créé pour les opérateurs bouclés). Ainsi,

a* 

est mis en œuvre comme

a{0,0x7FFFFFFF} 

et

a+ 

est implémenté comme

a{1,0x7FFFFFFF} 

qui est la raison pour laquelle vous voyez exactement les mêmes comportements pour Curly et étoiles .

+0

Il ne me venait pas à l'esprit de regarder la source de Pattern ... Silly moi. Je vous remercie! Cela prend tout son sens maintenant. –

+0

L'une des nombreuses raisons pour lesquelles je ne peux pas vivre sans Eclipse est mon doigt qui claque ctrl-click (qui, si vous n'utilisez pas Eclipse, signifie "ouvrir le fichier source où ce nom est défini"). –

+0

Merci, je n'ai jamais pris la peine d'attacher la source à Eclipse. Je le ferai maintenant pour sûr. Merci. –

13

Il est un bug: http://bugs.sun.com/view_bug.do?bug_id=6695369

Pattern.compile() est toujours censé lancer une exception si elle ne peut pas déterminer la longueur maximale d'un match de lookbehind.

+0

Salut Alan, je pensais que tu viendrais bientôt! Merci, je savais que c'était un bug (en 1.5) mais je me suis souvenu que le moteur évaluait toujours à faux ou quelque chose comme ça. Maintenant, il évalue correctement le résultat, donc j'ai pensé que le bug a été corrigé dans la version 1.6. Je me suis souvenu incorrectement. Merci pour l'info. Bart (aka prometheuzz) –

+0

Ce bug a été corrigé; c'est un nouveau qui a été introduit en 1.6. :/ –

+0

Ah, génial: |. Merci pour l'info. –

Questions connexes