1 /*
   2  * Copyright (c) 2012, 2018, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 
  25 package org.graalvm.compiler.debug;
  26 
  27 import java.util.Arrays;
  28 import java.util.regex.Pattern;
  29 
  30 import org.graalvm.compiler.debug.DebugContext.Scope;
  31 
  32 /**
  33  * Implements the filter specified by options such as {@link DebugOptions#Dump},
  34  * {@link DebugOptions#Log}, {@link DebugOptions#Count} and {@link DebugOptions#Time}.
  35  *
  36  * See <a href="DumpHelp.txt">here</a> for a description of the filter syntax.
  37  *
  38  * <p>
  39  * These options enable the associated debug facility if their filter matches the
  40  * {@linkplain Scope#getQualifiedName() name} of the current scope. For the
  41  * {@link DebugOptions#Dump} and {@link DebugOptions#Log} options, the log or dump level is set. The
  42  * {@link DebugOptions#Count} and {@link DebugOptions#Time} options don't have a level, for them
  43  * {@code level = 0} means disabled and a {@code level > 0} means enabled.
  44  * <p>
  45  * The syntax for a filter is explained <a href="file:doc-files/DumpHelp.txt">here</a>.
  46  */
  47 final class DebugFilter {
  48 
  49     public static DebugFilter parse(String spec) {
  50         if (spec == null) {
  51             return null;
  52         }
  53         return new DebugFilter(spec.split(","));
  54     }
  55 
  56     private final Term[] terms;
  57 
  58     private DebugFilter(String[] terms) {
  59         if (terms.length == 0) {
  60             this.terms = null;
  61         } else {
  62             this.terms = new Term[terms.length];
  63             for (int i = 0; i < terms.length; i++) {
  64                 String t = terms[i];
  65                 int idx = t.indexOf(':');
  66 
  67                 String pattern;
  68                 int level;
  69                 if (idx < 0) {
  70                     if (t.startsWith("~")) {
  71                         pattern = t.substring(1);
  72                         level = 0;
  73                     } else {
  74                         pattern = t;
  75                         level = DebugContext.BASIC_LEVEL;
  76                     }
  77                 } else {
  78                     pattern = t.substring(0, idx);
  79                     if (idx + 1 < t.length()) {
  80                         String levelString = t.substring(idx + 1);
  81                         try {
  82                             level = Integer.parseInt(levelString);
  83                         } catch (NumberFormatException e) {
  84                             switch (levelString) {
  85                                 case "basic":
  86                                     level = DebugContext.BASIC_LEVEL;
  87                                     break;
  88                                 case "info":
  89                                     level = DebugContext.INFO_LEVEL;
  90                                     break;
  91                                 case "verbose":
  92                                     level = DebugContext.VERBOSE_LEVEL;
  93                                     break;
  94                                 default:
  95                                     throw new IllegalArgumentException("Unknown dump level: \"" + levelString + "\" expected basic, info, verbose or an integer");
  96                             }
  97                         }
  98 
  99                     } else {
 100                         level = DebugContext.BASIC_LEVEL;
 101                     }
 102                 }
 103 
 104                 this.terms[i] = new Term(pattern, level);
 105             }
 106         }
 107     }
 108 
 109     /**
 110      * Check whether a given input is matched by this filter, and determine the log level.
 111      */
 112     public int matchLevel(String input) {
 113         if (terms == null) {
 114             return DebugContext.BASIC_LEVEL;
 115         } else {
 116             int defaultLevel = 0;
 117             int level = -1;
 118             for (Term t : terms) {
 119                 if (t.isMatchAny()) {
 120                     defaultLevel = t.level;
 121                 } else if (t.matches(input)) {
 122                     level = t.level;
 123                 }
 124             }
 125             return level == -1 ? defaultLevel : level;
 126         }
 127     }
 128 
 129     @Override
 130     public String toString() {
 131         StringBuilder buf = new StringBuilder("DebugFilter");
 132         if (terms != null) {
 133             buf.append(Arrays.toString(terms));
 134         } else {
 135             buf.append("[]");
 136         }
 137         return buf.toString();
 138     }
 139 
 140     private static class Term {
 141 
 142         private final Pattern pattern;
 143         public final int level;
 144 
 145         Term(String filter, int level) {
 146             this.level = level;
 147             if (filter.isEmpty() || filter.equals("*")) {
 148                 this.pattern = null;
 149             } else if (filter.contains("*") || filter.contains("?")) {
 150                 this.pattern = Pattern.compile(MethodFilter.createGlobString(filter));
 151             } else {
 152                 this.pattern = Pattern.compile(".*" + MethodFilter.createGlobString(filter) + ".*");
 153             }
 154         }
 155 
 156         /**
 157          * Determines if a given input is matched by this filter.
 158          */
 159         public boolean matches(String input) {
 160             return pattern == null || pattern.matcher(input).matches();
 161         }
 162 
 163         public boolean isMatchAny() {
 164             return pattern == null;
 165         }
 166 
 167         @Override
 168         public String toString() {
 169             return (pattern == null ? ".*" : pattern.toString()) + ":" + level;
 170         }
 171     }
 172 }