Coverage Report - org.jbehave.core.embedder.MetaFilter
 
Classes in this File Line Coverage Branch Coverage Complexity
MetaFilter
95%
22/23
100%
6/6
2.476
MetaFilter$DefaultMetaMatcher
100%
54/54
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.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  188
 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  10
         this(filterAsString, new PrintStreamEmbedderMonitor());
 68  10
     }
 69  
 
 70  53
     public MetaFilter(String filterAsString, EmbedderMonitor monitor) {
 71  53
         this.filterAsString = filterAsString == null ? "" : filterAsString;
 72  53
         this.monitor = monitor;
 73  53
         this.metaMatcher = createMetaMatcher(this.filterAsString);
 74  53
         this.metaMatcher.parse(filterAsString);
 75  53
     }
 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  53
         if (filterAsString.startsWith("groovy: ")) {
 85  6
             return new GroovyMetaMatcher();
 86  
         }
 87  47
         return new DefaultMetaMatcher();
 88  
     }
 89  
 
 90  
     public boolean allow(Meta meta) {
 91  132
         boolean allowed = this.metaMatcher.match(meta);
 92  132
         if (!allowed) {
 93  27
             monitor.metaNotAllowed(meta, this);
 94  
         }
 95  132
         return allowed;
 96  
     }
 97  
 
 98  
     public MetaMatcher metaMatcher() {
 99  1
         return metaMatcher;
 100  
     }
 101  
 
 102  
     public String asString() {
 103  32
         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  47
     public class DefaultMetaMatcher implements MetaMatcher {
 120  
 
 121  47
         private final Properties include = new Properties();
 122  47
         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  47
             parse(include, "+");
 134  47
             parse(exclude, "-");
 135  47
         }
 136  
 
 137  
         public boolean match(Meta meta) {
 138  
             boolean matched;
 139  115
             if (!include.isEmpty() && exclude.isEmpty()) {
 140  14
                 matched = match(include, meta);
 141  101
             } else if (include.isEmpty() && !exclude.isEmpty()) {
 142  13
                 matched = !match(exclude, meta);
 143  88
             } else if (!include.isEmpty() && !exclude.isEmpty()) {
 144  6
                 matched = match(merge(include, exclude), meta) && !match(exclude, meta);
 145  
             } else {
 146  82
                 matched = true;
 147  
             }
 148  115
             return matched;
 149  
         }
 150  
 
 151  
         private void parse(Properties properties, String prefix) {
 152  94
             properties.clear();
 153  94
             for (String found : found(prefix)) {
 154  39
                 Property property = new Property(StringUtils.removeStartIgnoreCase(found, prefix));
 155  39
                 properties.setProperty(property.getName(), property.getValue());
 156  39
             }
 157  94
         }
 158  
 
 159  
         private Set<String> found(String prefix) {
 160  94
             Matcher matcher = findAllPrefixed(prefix).matcher(filterAsString);
 161  94
             Set<String> found = new HashSet<String>();
 162  133
             while (matcher.find()) {
 163  39
                 found.add(matcher.group().trim());
 164  
             }
 165  94
             return found;
 166  
         }
 167  
 
 168  
         private Pattern findAllPrefixed(String prefix) {
 169  94
                         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  8
             }
 183  6
             return merged;
 184  
         }
 185  
 
 186  
         private boolean match(Properties properties, Meta meta) {
 187  37
             boolean matches = false;
 188  37
             for (Object key : properties.keySet()) {
 189  40
                 String property = (String) properties.get(key);
 190  40
                 for (String metaName : meta.getPropertyNames()) {
 191  80
                     if (key.equals(metaName)) {
 192  34
                         String value = meta.getProperty(metaName);
 193  34
                         if (StringUtils.isBlank(value)) {
 194  6
                             matches = true;
 195  28
                         } else if (property.contains("*")) {
 196  1
                             matches = value.matches(property.replace("*", ".*"));
 197  
                         } else {
 198  27
                             matches = properties.get(key).equals(value);
 199  
                         }
 200  
                     }
 201  80
                     if (matches) {
 202  26
                         break;
 203  
                     }
 204  54
                 }
 205  40
             }
 206  37
             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  17
                 Object matcher = groovyClass.newInstance();
 250  17
                 metaField.set(matcher, meta);
 251  17
                 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  
 }