##### Coverage Report - org.jbehave.core.embedder.MetaFilter

Classes in this File | Line Coverage | Branch Coverage | Complexity
MetaFilter |
95% | 22/23
100% | 6/6
| 2.4761904761904763;2.476
MetaFilter$DefaultMetaMatcher |
100% | 52/52
95% | 38/40
| 2.4761904761904763;2.476
MetaFilter$GroovyMetaMatcher |
57% | 11/19
N/A | N/A
| 2.4761904761904763;2.476
MetaFilter$MetaMatcher |
N/A | N/A
N/A | N/A
| 2.4761904761904763;2.476

1 |  |
     package org.jbehave.core.embedder;
2 |  |
3 |  |
     import groovy.lang.GroovyClassLoader;
4 |  |
5 |  |
     import java.lang.reflect.Field;
6 |  |
     import java.lang.reflect.InvocationTargetException;
7 |  |
     import java.lang.reflect.Method;
8 |  |
     import java.text.MessageFormat;
9 |  |
     import java.util.HashSet;
10 |  |
     import java.util.Properties;
11 |  |
     import java.util.Set;
12 |  |
     import java.util.regex.Matcher;
13 |  |
     import java.util.regex.Pattern;
14 |  |
15 |  |
     import org.apache.commons.lang.StringUtils;
16 |  |
     import org.apache.commons.lang.builder.ToStringBuilder;
17 |  |
     import org.apache.commons.lang.builder.ToStringStyle;
18 |  |
     import org.jbehave.core.model.Meta;
19 |  |
     import org.jbehave.core.model.Meta.Property;
20 |  |
21 |  |
     /**
22 |  |
      * <p>
23 |  |
      * Allows filtering on meta info.
24 |  |
      * </p>
25 |  |
      *
26 |  |
      * <p>
27 |  |
      * A filter is uniquely identified by its String representation which is parsed
28 |  |
      * and matched by the {@link MetaMatcher} to determine if the {@link Meta} is
29 |  |
      * allowed or not.
30 |  |
      * </p>
31 |  |
      *
32 |  |
      * <p>
33 |  |
      * The {@link DefaultMetaMatcher} interprets the filter as a sequence of any
34 |  |
      * name-value properties (separated by a space), prefixed by "+" for inclusion
35 |  |
      * and "-" for exclusion. E.g.:
36 |  |
      *
37 |  |
      * <pre>
38 |  |
      * MetaFilter filter = new MetaFilter("+author Mauro -theme smoke testing +map *API -skip");
39 |  |
      * filter.allow(new Meta(asList("map someAPI")));
40 |  |
      * </pre>
41 |  |
      *
42 |  |
      * </p>
43 |  |
      * <p>
44 |  |
      * The use of the {@link GroovyMetaMatcher} is triggered by the prefix "groovy:" and
45 |  |
      * allows the filter to be interpreted as a Groovy expression.
46 |  |
      * </p>
47 |  |
      * <pre>
48 |  |
      * MetaFilter filter = new MetaFilter("groovy: (a == '11' | a == '22') && b == '33'");
49 |  |
      * </pre>
50 |  |
      */
51 | 176 |
     public class MetaFilter {
52 |  |
53 | 1 |
             private static String DEFAULT_META_PREFIX_PATTERN = "(\\{0}(\\w|\\.|\\,|\\;|\\:|\\!|\\$|\\&|\\s|\\*)*)";
54 |  |
55 | 1 |
         public static final MetaFilter EMPTY = new MetaFilter();
56 |  |
57 |  |
         private final String filterAsString;
58 |  |
         private final EmbedderMonitor monitor;
59 |  |
60 |  |
         private MetaMatcher metaMatcher;
61 |  |
62 |  |
         public MetaFilter() {
63 | 1 |
             this("");
64 | 1 |
         }
65 |  |
66 |  |
         public MetaFilter(String filterAsString) {
67 | 9 |
             this(filterAsString, new PrintStreamEmbedderMonitor());
68 | 9 |
         }
69 |  |
70 | 50 |
         public MetaFilter(String filterAsString, EmbedderMonitor monitor) {
71 | 50 |
             this.filterAsString = filterAsString == null ? "" : filterAsString;
72 | 50 |
             this.monitor = monitor;
73 | 50 |
             this.metaMatcher = createMetaMatcher(this.filterAsString);
74 | 50 |
             this.metaMatcher.parse(filterAsString);
75 | 50 |
         }
76 |  |
77 |  |
         /**
78 |  |
          * Creates a MetaMatcher based on the filter content.
79 |  |
          *
80 |  |
          * @param filterAsString the String representation of the filter
81 |  |
          * @return A MetaMatcher
82 |  |
          */
83 |  |
         protected MetaMatcher createMetaMatcher(String filterAsString) {
84 | 50 |
             if (filterAsString.startsWith("groovy: ")) {
85 | 6 |
                 return new GroovyMetaMatcher();
86 |  |
             }
87 | 44 |
             return new DefaultMetaMatcher();
88 |  |
         }
89 |  |
90 |  |
         public boolean allow(Meta meta) {
91 | 1117 |
             boolean allowed = this.metaMatcher.match(meta);
92 | 1117 |
             if (!allowed) {
93 | 1024 |
                 monitor.metaNotAllowed(meta, this);
94 |  |
             }
95 | 1117 |
             return allowed;
96 |  |
         }
97 |  |
98 |  |
         public MetaMatcher metaMatcher() {
99 | 1 |
             return metaMatcher;
100 |  |
         }
101 |  |
102 |  |
         public String asString() {
103 | 1029 |
             return filterAsString;
104 |  |
         }
105 |  |
106 |  |
         @Override
107 |  |
         public String toString() {
108 | 0 |
             return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
109 |  |
         }
110 |  |
111 |  |
         public interface MetaMatcher {
112 |  |
113 |  |
             void parse(String filterAsString);
114 |  |
115 |  |
             boolean match(Meta meta);
116 |  |
117 |  |
         }
118 |  |
119 | 44 |
         public class DefaultMetaMatcher implements MetaMatcher {
120 |  |
121 | 44 |
             private final Properties include = new Properties();
122 | 44 |
             private final Properties exclude = new Properties();
123 |  |
124 |  |
             public Properties include() {
125 | 1 |
                 return include;
126 |  |
             }
127 |  |
128 |  |
             public Properties exclude() {
129 | 1 |
                 return exclude;
130 |  |
             }
131 |  |
132 |  |
             public void parse(String filterAsString) {
133 | 44 |
                 parse(include, "+");
134 | 44 |
                 parse(exclude, "-");
135 | 44 |
             }
136 |  |
137 |  |
             public boolean match(Meta meta) {
138 |  |
                 boolean matched;
139 | 101 |
                 if (!include.isEmpty() && exclude.isEmpty()) {
140 | 12 |
                     matched = match(include, meta);
141 | 89 |
                 } else if (include.isEmpty() && !exclude.isEmpty()) {
142 | 13 |
                     matched = !match(exclude, meta);
143 | 76 |
                 } else if (!include.isEmpty() && !exclude.isEmpty()) {
144 | 6 |
                     matched = match(merge(include, exclude), meta) && !match(exclude, meta);
145 |  |
                 } else {
146 | 70 |
                     matched = true;
147 |  |
                 }
148 | 101 |
                 return matched;
149 |  |
             }
150 |  |
151 |  |
             private void parse(Properties properties, String prefix) {
152 | 88 |
                 properties.clear();
153 | 88 |
                 for (String found : found(prefix)) {
154 | 38 |
                     Property property = new Property(StringUtils.removeStartIgnoreCase(found, prefix));
155 | 38 |
                     properties.setProperty(property.getName(), property.getValue());
156 | 38 |
                 }
157 | 88 |
             }
158 |  |
159 |  |
             private Set<String> found(String prefix) {
160 | 88 |
                 Matcher matcher = findAllPrefixed(prefix).matcher(filterAsString);
161 | 88 |
                 Set<String> found = new HashSet<String>();
162 | 126 |
                 while (matcher.find()) {
163 | 38 |
                     found.add(matcher.group().trim());
164 |  |
                 }
165 | 88 |
                 return found;
166 |  |
             }
167 |  |
168 |  |
             private Pattern findAllPrefixed(String prefix) {
169 | 88 |
                             return Pattern.compile(MessageFormat.format(DEFAULT_META_PREFIX_PATTERN, prefix), Pattern.DOTALL);
170 |  |
             }
171 |  |
172 |  |
             private Properties merge(Properties include, Properties exclude) {
173 | 6 |
                 Set<Object> in = new HashSet<Object>(include.keySet());
174 | 6 |
                 in.addAll(exclude.keySet());
175 | 6 |
                 Properties merged = new Properties();
176 | 6 |
                 for (Object key : in) {
177 | 8 |
                     if (include.containsKey(key)) {
178 | 6 |
                         merged.put(key, include.get(key));
179 | 2 |
                     } else if (exclude.containsKey(key)) {
180 | 2 |
                         merged.put(key, exclude.get(key));
181 |  |
                     }
182 |  |
                 }
183 | 6 |
                 return merged;
184 |  |
             }
185 |  |
186 |  |
             private boolean match(Properties properties, Meta meta) {
187 | 35 |
                 boolean matches = false;
188 | 35 |
                 for (Object key : properties.keySet()) {
189 | 38 |
                     String property = (String) properties.get(key);
190 | 38 |
                     for (String metaName : meta.getPropertyNames()) {
191 | 69 |
                         if (key.equals(metaName)) {
192 | 32 |
                             String value = meta.getProperty(metaName);
193 | 32 |
                             if (StringUtils.isBlank(value)) {
194 | 6 |
                                 matches = true;
195 | 26 |
                             } else if (property.contains("*")) {
196 | 1 |
                                 matches = value.matches(property.replace("*", ".*"));
197 |  |
                             } else {
198 | 25 |
                                 matches = properties.get(key).equals(value);
199 |  |
                             }
200 |  |
                         }
201 | 69 |
                         if (matches) {
202 | 26 |
                             break;
203 |  |
                         }
204 |  |
                     }
205 | 38 |
                 }
206 | 35 |
                 return matches;
207 |  |
             }
208 |  |
209 |  |
         }
210 |  |
211 | 6 |
         public class GroovyMetaMatcher implements MetaMatcher {
212 |  |
213 |  |
             private Class<?> groovyClass;
214 |  |
             private Field metaField;
215 |  |
             private Method match;
216 |  |
217 |  |
             public void parse(String filterAsString) {
218 | 6 |
                 String groovyString = "public class GroovyMatcher {\n" +
219 |  |
                         "public org.jbehave.core.model.Meta meta\n" +
220 |  |
                         "  public boolean match() {\n" +
221 |  |
                         "    return (" + filterAsString.substring("groovy: ".length()) + ")\n" +
222 |  |
                         "  }\n" +
223 |  |
                         "  def propertyMissing(String name) {\n" +
224 |  |
                         "    if (!meta.hasProperty(name)) {\n" +
225 |  |
                         "      return false\n" +
226 |  |
                         "    }\n" +
227 |  |
                         "    def v = meta.getProperty(name)\n" +
228 |  |
                         "    if (v.equals('')) {\n" +
229 |  |
                         "      return true\n" +
230 |  |
                         "    }\n" +
231 |  |
                         "    return v\n" +
232 |  |
                         "  }\n" +
233 |  |
                         "}";
234 |  |
235 | 6 |
                 GroovyClassLoader loader = new GroovyClassLoader(getClass().getClassLoader());
236 | 6 |
                 groovyClass = loader.parseClass(groovyString);
237 |  |
                 try {
238 | 6 |
                     match = groovyClass.getDeclaredMethod("match");
239 | 6 |
                     metaField = groovyClass.getField("meta");
240 | 0 |
                 } catch (NoSuchFieldException e) {
241 |  |
                     // can never occur as we control the groovy string
242 | 0 |
                 } catch (NoSuchMethodException e) {
243 |  |
                     // can never occur as we control the groovy string
244 | 6 |
                 }
245 | 6 |
             }
246 |  |
247 |  |
             public boolean match(Meta meta) {
248 |  |
                 try {
249 | 1016 |
                     Object matcher = groovyClass.newInstance();
250 | 1016 |
                     metaField.set(matcher, meta);
251 | 1016 |
                     return (Boolean) match.invoke(matcher);
252 | 0 |
                 } catch (InstantiationException e) {
253 | 0 |
                     throw new RuntimeException(e);
254 | 0 |
                 } catch (IllegalAccessException e) {
255 | 0 |
                     throw new RuntimeException(e);
256 | 0 |
                 } catch (InvocationTargetException e) {
257 | 0 |
                     throw new RuntimeException(e);
258 |  |
                 }
259 |  |
             }
260 |  |
         }
261 |  |
262 |  |
     }