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