Initial import
[jpf-core.git] / src / main / gov / nasa / jpf / util / StringSetMatcher.java
1 /*
2  * Copyright (C) 2014, United States Government, as represented by the
3  * Administrator of the National Aeronautics and Space Administration.
4  * All rights reserved.
5  *
6  * The Java Pathfinder core (jpf-core) platform is licensed under the
7  * Apache License, Version 2.0 (the "License"); you may not use this file except
8  * in compliance with the License. You may obtain a copy of the License at
9  * 
10  *        http://www.apache.org/licenses/LICENSE-2.0. 
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and 
16  * limitations under the License.
17  */
18 package gov.nasa.jpf.util;
19
20 import java.util.regex.Matcher;
21 import java.util.regex.Pattern;
22
23 /**
24  * simple utility that can be used to check for string matches in
25  * sets with '*' wildcards, e.g. to check for class name lists such as
26  *
27  *   vm.halt_on_throw=java.lang.reflect.*:my.own.Exception
28  *
29  * Only meta chars in patterns are '*' and '!', i.e. '.' is a regular char to match
30  * A '!' prefix inverts the match
31  */
32 public class StringSetMatcher {
33
34   public static final char WILDCARD = '*';
35   public static final char INVERTED = '!';
36   
37   boolean hasAnyPattern; // do we have a universal '*' pattern?
38
39   Pattern[] pattern;
40   Matcher[] matcher;
41   boolean[] inverted;
42
43   /**
44    * convenience method for matcher pairs containing of explicit excludes and
45    * includes
46    */
47   public static boolean isMatch (String s, StringSetMatcher includes, StringSetMatcher excludes){
48     if (excludes != null) {
49       if (excludes.matchesAny(s)){
50         return false;
51       }
52     }
53
54     if (includes != null) {
55       if (!includes.matchesAny(s)){
56         return false;
57       }
58     }
59
60     return true;
61   }
62
63   public static StringSetMatcher getNonEmpty(String[] set){
64     if (set != null && set.length > 0){
65       return new StringSetMatcher(set);
66     } else {
67       return null;
68     }
69   }
70
71   public StringSetMatcher (String... set){
72     int n = set.length;
73     pattern = new Pattern[n];
74     matcher = new Matcher[n];
75     inverted = new boolean[n];
76
77     for (int i=0; i<n; i++){
78       String s = set[i];
79
80       if (s.equals("*")) {
81         hasAnyPattern = true;
82         // no need to compile this
83
84       } else {
85         Pattern p =  createPattern(s);
86         pattern[i] = p;
87         matcher[i] = p.matcher(""); // gets reset upon use
88         inverted[i] = isInverted(s);
89       }
90     }
91   }
92
93   @Override
94   public String toString() {
95     int n=0;
96     StringBuilder sb = new StringBuilder(64);
97     sb.append("StringSetMatcher {patterns=");
98
99     if (hasAnyPattern) {
100       sb.append(".*");
101       n++;
102     }
103
104     for (int i=0; i<pattern.length; i++) {
105       if (pattern[i] != null) {
106         if (n++>0) {
107           sb.append(',');
108         }
109         if (inverted[i]){
110           sb.append(INVERTED);
111         }
112         sb.append(pattern[i]);
113       }
114     }
115     sb.append('}');
116     return sb.toString();
117   }
118
119   public void addPattern (String s){
120
121     if (s.equals("*")) { // no need to compile
122       // note that this doesn't include the - pointless - "!*", which would match nothing
123       hasAnyPattern = true;
124
125     } else {
126       int n = pattern.length;
127
128       Pattern[] pNew = new Pattern[n+1];
129       System.arraycopy(pattern, 0, pNew, 0, n);
130       pNew[n] = createPattern(s);
131
132       Matcher[] mNew = new Matcher[pNew.length];
133       System.arraycopy(matcher, 0, mNew, 0, n);
134       mNew[n] = pNew[n].matcher("");
135
136       boolean[] iNew = new boolean[pNew.length];
137       System.arraycopy( inverted, 0, iNew, 0, n);
138       iNew[n] = isInverted(s);
139       
140       pattern = pNew;
141       matcher = mNew;
142       inverted = iNew;
143     }
144   }
145
146   public static boolean isInverted (String s){
147     return (!s.isEmpty() && s.charAt(0) == INVERTED);
148   }
149   
150   protected Pattern createPattern (String s){
151     Pattern p;
152     int j = 0;
153     int len = s.length();
154
155     // inversion is better done outside of regex
156     if ((len > 0) && s.charAt(0) == INVERTED){
157       j++; // skip INVERTED char
158     }
159     
160     StringBuilder sb = new StringBuilder();
161         
162     for (; j<len; j++){
163       char c = s.charAt(j);
164       switch (c){
165       case '.' : sb.append("\\."); break;
166       case '$' : sb.append("\\$"); break;
167       case '[' : sb.append("\\["); break;
168       case ']' : sb.append("\\]"); break;
169       case '*' : sb.append(".*"); break;
170       case '(' : sb.append("\\("); break;
171       case ')' : sb.append("\\)"); break;
172       // <2do> and probably more..
173       default:   sb.append(c);
174       }
175     }
176
177     p = Pattern.compile(sb.toString());
178     return p;
179   }
180
181   /**
182    * does 's' match at least one of our patterns
183    */
184   public boolean matchesAny (String s){
185     if (s != null) {
186       if (hasAnyPattern) {
187         return true; // no need to check
188       }
189
190       for (int i=0; i<matcher.length; i++){
191         Matcher m = matcher[i];
192         m.reset(s);
193
194         if (m.matches() != inverted[i]){
195           return true;
196         }
197       }
198     }
199
200     return false;
201   }
202
203   /**
204    * does 's' match ALL of our patterns
205    */
206   public boolean matchesAll (String s){
207     if (s != null) {
208       if (hasAnyPattern && pattern.length == 1) { // there might be other patterns
209         return true; // no need to check
210       }
211
212       for (int i=0; i<pattern.length; i++){
213         Pattern p = pattern[i];
214         if (p != null){
215           Matcher m = matcher[i];
216           m.reset(s);
217
218           if (m.matches() == inverted[i]){
219             return false;
220           }
221         } else {
222           if (inverted[i]){
223             return false;
224           }
225         }
226       }
227
228       return true;
229
230     } else {
231       return false;
232     }
233   }
234
235   /**
236    * do all elements of 'set' match at least one of our patterns?
237    */
238   public boolean allMatch (String[] set){
239     if (hasAnyPattern) {
240       return true;
241     }
242
243     for (int i=0; i<set.length; i++){
244       if (!matchesAny(set[i])){
245         return false;
246       }
247     }
248     return true;
249   }
250
251
252   public static void main (String[] args){
253     String[] p = args[0].split(":");
254     String[] s = args[1].split(":");
255
256     StringSetMatcher sm = new StringSetMatcher(p);
257     if (sm.matchesAny(s[0])){
258       System.out.println("Bingo, \"" + s[0] + "\" matches " + sm);
259     } else {
260       System.out.println("nope, \"" + s[0] + "\" doesn't match " + sm);
261     }
262   }
263 }