1 /*
   2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.util.regex;
  27 
  28 import java.util.HashMap;
  29 import java.util.regex.Pattern.CharPredicate;
  30 import java.util.regex.CharPredicates;
  31 import static java.util.regex.ASCII.*;
  32 
  33 /**
  34  * A utility class to print out the pattern node tree.
  35  */
  36 
  37 class PrintPattern {
  38 
  39     private static HashMap<Pattern.Node, Integer> ids = new HashMap<>();
  40 
  41     private static void print(Pattern.Node node, String text, int depth) {
  42         if (!ids.containsKey(node))
  43             ids.put(node, ids.size());
  44         print("%6d:%" + (depth==0? "": depth<<1) + "s<%s>", ids.get(node), "", text);
  45         if (ids.containsKey(node.next))
  46             print(" (=>%d)", ids.get(node.next));
  47         print("%n");
  48     }
  49 
  50     private static void print(String s, int depth) {
  51         print("       %" + (depth==0?"":depth<<1) + "s<%s>%n", "", s);
  52     }
  53 
  54     private static void print(String fmt, Object ... args) {
  55         System.err.printf(fmt, args);
  56     }
  57 
  58     private static String toStringCPS(int[] cps) {
  59         StringBuilder sb = new StringBuilder(cps.length);
  60         for (int cp : cps)
  61             sb.append(toStringCP(cp));
  62         return sb.toString();
  63     }
  64 
  65     private static String toStringCP(int cp) {
  66         return (isPrint(cp) ? "" + (char)cp
  67                             : "\\u" + Integer.toString(cp, 16));
  68     }
  69 
  70     private static String toStringRange(int min, int max) {
  71        if (max == Pattern.MAX_REPS) {
  72            if (min == 0)
  73                return " * ";
  74            else if (min == 1)
  75                return " + ";
  76            return "{" + min + ", max}";
  77        }
  78        return "{" + min + ", " +  max + "}";
  79     }
  80 
  81     private static String toStringCtype(int type) {
  82         switch(type) {
  83         case UPPER:  return "ASCII.UPPER";
  84         case LOWER:  return "ASCII.LOWER";
  85         case DIGIT:  return "ASCII.DIGIT";
  86         case SPACE:  return "ASCII.SPACE";
  87         case PUNCT:  return "ASCII.PUNCT";
  88         case CNTRL:  return "ASCII.CNTRL";
  89         case BLANK:  return "ASCII.BLANK";
  90         case UNDER:  return "ASCII.UNDER";
  91         case ASCII:  return "ASCII.ASCII";
  92         case ALPHA:  return "ASCII.ALPHA";
  93         case ALNUM:  return "ASCII.ALNUM";
  94         case GRAPH:  return "ASCII.GRAPH";
  95         case WORD:   return "ASCII.WORD";
  96         case XDIGIT: return "ASCII.XDIGIT";
  97         default: return "ASCII ?";
  98         }
  99     }
 100 
 101     private static String toString(Pattern.Node node) {
 102         String name = node.getClass().getName();
 103         return name.substring(name.lastIndexOf('$') + 1);
 104     }
 105 
 106     static HashMap<CharPredicate, String> pmap;
 107     static {
 108         pmap = new HashMap<>();
 109         pmap.put(Pattern.ALL, "All");
 110         pmap.put(Pattern.DOT, "Dot");
 111         pmap.put(Pattern.UNIXDOT, "UnixDot");
 112         pmap.put(Pattern.VertWS, "VertWS");
 113         pmap.put(Pattern.HorizWS, "HorizWS");
 114 
 115         pmap.put(CharPredicates.ASCII_DIGIT, "ASCII.DIGIT");
 116         pmap.put(CharPredicates.ASCII_WORD,  "ASCII.WORD");
 117         pmap.put(CharPredicates.ASCII_SPACE, "ASCII.SPACE");
 118     }
 119 
 120     static void walk(Pattern.Node node, int depth) {
 121         depth++;
 122         while(node != null) {
 123             String name = toString(node);
 124             String str;
 125             if (node instanceof Pattern.Prolog) {
 126                 print(node, name, depth);
 127                 // print the loop here
 128                 Pattern.Loop loop = ((Pattern.Prolog)node).loop;
 129                 name = toString(loop);
 130                 str = name + " " + toStringRange(loop.cmin, loop.cmax);
 131                 print(loop, str, depth);
 132                 walk(loop.body, depth);
 133                 print("/" + name, depth);
 134                 node = loop;
 135             } else if (node instanceof Pattern.Loop) {
 136                 return;  // stop here, body.next -> loop
 137             } else if (node instanceof Pattern.Curly) {
 138                 Pattern.Curly c = (Pattern.Curly)node;
 139                 str = "Curly " + c.type + " " + toStringRange(c.cmin, c.cmax);
 140                 print(node, str, depth);
 141                 walk(c.atom, depth);
 142                 print("/Curly", depth);
 143             } else if (node instanceof Pattern.GroupCurly) {
 144                 Pattern.GroupCurly gc = (Pattern.GroupCurly)node;
 145                 str = "GroupCurly " + gc.groupIndex / 2 +
 146                       ", " + gc.type + " " + toStringRange(gc.cmin, gc.cmax);
 147                 print(node, str, depth);
 148                 walk(gc.atom, depth);
 149                 print("/GroupCurly", depth);
 150             } else if (node instanceof Pattern.GroupHead) {
 151                 Pattern.GroupHead head = (Pattern.GroupHead)node;
 152                 Pattern.GroupTail tail = head.tail;
 153                 print(head, "Group.head " + (tail.groupIndex / 2), depth);
 154                 walk(head.next, depth);
 155                 print(tail, "/Group.tail " + (tail.groupIndex / 2), depth);
 156                 node = tail;
 157             } else if (node instanceof Pattern.GroupTail) {
 158                 return;  // stopper
 159             } else if (node instanceof Pattern.Ques) {
 160                 print(node, "Ques " + ((Pattern.Ques)node).type, depth);
 161                 walk(((Pattern.Ques)node).atom, depth);
 162                 print("/Ques", depth);
 163             } else if (node instanceof Pattern.Branch) {
 164                 Pattern.Branch b = (Pattern.Branch)node;
 165                 print(b, name, depth);
 166                 int i = 0;
 167                 while (true) {
 168                     if (b.atoms[i] != null) {
 169                         walk(b.atoms[i], depth);
 170                     } else {
 171                         print("  (accepted)", depth);
 172                     }
 173                     if (++i == b.size)
 174                         break;
 175                     print("-branch.separator-", depth);
 176                 }
 177                 node = b.conn;
 178                 print(node, "/Branch", depth);
 179             } else if (node instanceof Pattern.BranchConn) {
 180                 return;
 181             } else if (node instanceof Pattern.CharProperty) {
 182                 str = pmap.get(((Pattern.CharProperty)node).predicate);
 183                 if (str == null)
 184                     str = toString(node);
 185                 else
 186                     str = "Single \"" + str + "\"";
 187                 print(node, str, depth);
 188             } else if (node instanceof Pattern.SliceNode) {
 189                 str = name + "  \"" +
 190                       toStringCPS(((Pattern.SliceNode)node).buffer) + "\"";
 191                 print(node, str, depth);
 192             } else if (node instanceof Pattern.CharPropertyGreedy) {
 193                 Pattern.CharPropertyGreedy gcp = (Pattern.CharPropertyGreedy)node;
 194                 String pstr = pmap.get(gcp.predicate);
 195                 if (pstr == null)
 196                     pstr = gcp.predicate.toString();
 197                 else
 198                     pstr = "Single \"" + pstr + "\"";
 199                 str = name + " " + pstr + ((gcp.cmin == 0) ? "*" : "+");
 200                 print(node, str, depth);
 201             } else if (node instanceof Pattern.BackRef) {
 202                 str = "GroupBackRef " + ((Pattern.BackRef)node).groupIndex / 2;
 203                 print(node, str, depth);
 204             } else if (node instanceof Pattern.LastNode) {
 205                 print(node, "END", depth);
 206             } else if (node == Pattern.accept) {
 207                 return;
 208             } else {
 209                 print(node, name, depth);
 210             }
 211             node = node.next;
 212         }
 213     }
 214 
 215     public static void main(String[] args) {
 216         Pattern p = Pattern.compile(args[0]);
 217         System.out.println("   Pattern: " + p);
 218         walk(p.root, 0);
 219     }
 220 }