com.dtolabs.rundeck.core.utils.NodeSet.java Source code

Java tutorial

Introduction

Here is the source code for com.dtolabs.rundeck.core.utils.NodeSet.java

Source

/*
 * Copyright 2010 DTO Labs, Inc. (http://dtolabs.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.dtolabs.rundeck.core.utils;

import com.dtolabs.rundeck.core.common.INodeEntry;
import com.dtolabs.rundeck.core.common.NodesSelector;
import com.dtolabs.rundeck.core.common.SelectorUtils;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.util.*;
import java.util.regex.PatternSyntaxException;

/**
 * NodeSet provides filtering logic for Node criteria
 */
public class NodeSet implements NodesSelector {
    public static final String HOSTNAME = "hostname";
    public static final String NAME = "name";
    public static final String TYPE = "type";
    public static final String TAGS = "tags";
    public static final String OS_NAME = "os-name";
    public static final String OS_FAMILY = "os-family";
    public static final String OS_ARCH = "os-arch";
    public static final String OS_VERSION = "os-version";

    private String singleNodeName;
    public static final String DEFAULT_FILTER_KEY = "name";

    /**
     * default constructor
     */
    public NodeSet() {
    }

    /**
     * Create a nodeset with a single node name
     */
    public NodeSet(final String singleNodeName) {
        this.singleNodeName = singleNodeName;
    }

    /**
     * Create a nodeset for a single node
     */
    public NodeSet(final INodeEntry singleNode) {
        this(singleNode.getNodename());
    }

    /**
     * names of the filter attributes used by exclude/include that are exposed to the CLI interface
     */
    private static final String[] FILTER_KEYS = { HOSTNAME, NAME, TYPE, TAGS, OS_NAME, OS_FAMILY, OS_ARCH,
            OS_VERSION, };
    /**
     * names of the filter attributes used by exclude/include that are exposed to the CLI interface
     */
    public static final List<String> FILTER_KEYS_LIST = Collections.unmodifiableList(Arrays.asList(FILTER_KEYS));

    public String getSingleNodeName() {
        return singleNodeName;
    }

    public void setSingleNodeName(final String singleNodeName) {
        this.singleNodeName = singleNodeName;
    }

    public boolean acceptNode(final INodeEntry entry) {
        return !shouldExclude(entry);
    }

    /**
     * Return a new Node Selector that will apply the include/exclude filters if they are set, otherwise
     * it only includes the node with the given nodename
     */
    public NodesSelector nodeSelectorWithDefault(final String nodename) {
        final NodesSelector nodesSelector = SelectorUtils.singleNode(nodename);
        return new NodesSelector() {
            public boolean acceptNode(final INodeEntry entry) {
                return !isBlank() ? NodeSet.this.acceptNode(entry) : nodesSelector.acceptNode(entry);
            }
        };
    }

    /**
     * Return a new Node Selector that will apply the include/exclude filters if they are set, otherwise
     * it will accept all nodes
     */
    public NodesSelector nodeSelectorWithDefaultAll() {
        return new NodesSelector() {
            public boolean acceptNode(final INodeEntry entry) {
                return isBlank() || NodeSet.this.acceptNode(entry);
            }
        };
    }

    /**
     * Enum of filters.
     * Implements a visitor pattern for SetSelector instances, allowing the enum to get the appropriate property
     * value of a SetSelector instances with the {@link #value(NodeSet.SetSelector)}
     * method.
     */
    public static enum FILTER_ENUM {

        F_HOSTNAME("hostname") {
            public String value(SetSelector set) {
                return set.getHostname();
            }
        },
        F_NAME("name") {
            public String value(SetSelector set) {
                return set.getName();
            }
        },
        F_TAGS("tags") {
            public String value(SetSelector set) {
                return set.getTags();
            }
        },
        F_OS_NAME("os-name") {
            public String value(SetSelector set) {
                return set.getOsname();
            }
        },
        F_OS_FAMILY("os-family") {
            public String value(SetSelector set) {
                return set.getOsfamily();
            }
        },
        F_OS_ARCH("os-arch") {
            public String value(SetSelector set) {
                return set.getOsarch();
            }
        },
        F_OS_VERSION("os-version") {
            public String value(SetSelector set) {
                return set.getOsversion();
            }
        };

        private final String name;

        FILTER_ENUM(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        /**
         * Return the value of the property matching the filter key for the given selector set
         * @param set the SetSelector
         * @return the property value
         */
        public abstract String value(SetSelector set);
    }

    /**
     * Collection of names of attributes used by exclude/include that are exposed to the CLI interface
     */
    public static final Collection<String> FILTER_KEYSET = new HashSet<String>(FILTER_KEYS_LIST);
    private Include includes;

    private Exclude excludes;
    private int threadCount = 1;
    private boolean keepgoing = false;
    private File failedNodesfile;

    /**
     * Return true if the include/exclude filters are blank or non-existent
     * @return true if all filters are blank or empty
     */
    public boolean isBlank() {
        return (null == getInclude() || getInclude().isBlank()) && (null == getExclude() || getExclude().isBlank())
                && null == getSingleNodeName();
    }

    public Include createInclude() {
        if (null != includes) {
            throw new IllegalStateException("only one include is allowed");
        }
        includes = new Include();
        return includes;
    }

    public Exclude createExclude() {
        if (null != excludes) {
            throw new IllegalStateException("only one exclude is allowed");
        }
        excludes = new Exclude();
        return excludes;
    }

    public Include getInclude() {
        return includes;
    }

    public Exclude getExclude() {
        return excludes;
    }

    /**
     * Return true if the node entry should be excluded based on the includes and excludes parameters.
     * When both include and exclude patterns match the node, it will be excluded based on which filterset is dominant.
     * @param entry node descriptor entry
     * @return true if the node should be excluded.
     */
    public boolean shouldExclude(final INodeEntry entry) {
        if (null != getSingleNodeName()) {
            return !getSingleNodeName().equals(entry.getNodename());
        }

        boolean includesMatch = includes != null && includes.matches(entry);
        boolean excludesMatch = excludes != null && excludes.matches(entry);
        if (null == excludes || excludes.isBlank()) {
            return !includesMatch;
        } else if (null == includes || includes.isBlank()) {
            return excludesMatch;
        } else if (null != includes && includes.isDominant()) {
            return !includesMatch && excludesMatch;
        } else {
            return !includesMatch || excludesMatch;
        }
    }

    public int getThreadCount() {
        return threadCount;
    }

    public void setThreadCount(int threadCount) {
        this.threadCount = threadCount;
    }

    public boolean isKeepgoing() {
        return keepgoing;
    }

    public void setKeepgoing(boolean keepgoing) {
        this.keepgoing = keepgoing;
    }

    /**
     * Validate input. If failedNodesfile looks like an invalid property reference, set it to null.
     */
    public void validate() {
        if (null != failedNodesfile && failedNodesfile.getName().startsWith("${")
                && failedNodesfile.getName().endsWith("}")) {
            failedNodesfile = null;
        }
    }

    /**
     * Return true if the input selector matches the specified property value
     * @param inputSelector
     * @param propValue
     * @return
     */
    protected static boolean matchesInput(String inputSelector, String propValue) {
        if (null == propValue || "".equals(propValue.trim())) {
            return false;
        }
        if (null == inputSelector || "".equals(inputSelector.trim())) {
            return false;
        }
        List list = Arrays.asList(inputSelector.split(","));
        return matchRegexOrEquals(inputSelector, propValue) || list.contains(propValue);

    }

    /**
     * Return true if any attribute selector matches the corresponding attribute value
     * @param matchAll if true, require all selectors match a value, otherwise return true if any selector matches
     * @return
     */
    protected static boolean matchesInput(Map<String, String> attrSelectors, Map<String, String> values,
            boolean matchAll) {
        if (null == attrSelectors || null == values) {
            return false;
        }
        for (final Map.Entry<String, String> entry : attrSelectors.entrySet()) {
            final boolean match = matchesInput(entry.getValue(), values.get(entry.getKey()));
            if (!match && matchAll) {
                return false;
            } else if (match && !matchAll) {
                return true;
            }
        }
        //if matchAll, then all matches succeeded by now. return true, == matchAll.
        //if !matchAll, then no match succeeded by now. return false == matchAll.
        return matchAll;
    }

    /**
     * Return true if the input string is found in the set of values.
     * The inputSelector can contain boolean and using the "+" symbol, and boolean or using the "," symbol.
     * E.g.:  "a + b" - matches if both "a" and "b" are in the value set
     * E.g.:  "a , b" - matches if either "a" or "b" are in the value set
     *
     * @param inputSelector
     *
     * @return
     */
    static boolean matchesInputSet(String inputSelector, Collection propSet) {
        if (null == propSet || propSet.size() < 1) {
            return false;
        }
        if (null == inputSelector || "".equals(inputSelector.trim())) {
            return false;
        }
        if (inputSelector.contains("+") || inputSelector.contains(",")) {
            HashSet orSet = new HashSet();
            orSet.addAll(Arrays.asList(inputSelector.split(",")));
            for (Object anOrSet : orSet) {
                String clause = (String) anOrSet;
                HashSet set = new HashSet();
                set.addAll(Arrays.asList(clause.split("\\+")));
                boolean found = true;
                for (Object aSet : set) {
                    String tag = (String) aSet;

                    if (!propSet.contains(tag.trim())) {
                        //try regular expression match
                        boolean rematch = false;
                        for (Object aPropSet : propSet) {
                            String item = (String) aPropSet;
                            if (matchRegexOrEquals(tag, item)) {
                                rematch = true;
                                break;
                            }
                        }
                        if (!rematch) {
                            found = false;
                        }
                    }
                }
                if (found) {
                    return true;
                }
            }
            return false;
        } else {
            boolean contain = propSet.contains(inputSelector);
            boolean rematch = false;
            for (Object aPropSet : propSet) {
                String item = (String) aPropSet;
                if (matchRegexOrEquals(inputSelector, item)) {
                    rematch = true;
                    break;
                }
            }
            return rematch || contain;
        }
    }

    /**
     * Tests whether the selector string matches the item, in three possible ways: first, if the selector looks like:
     * "/.../" then it the outer '/' chars are removed and it is treated as a regular expression *only* and
     * PatternSyntaxExceptions are not caught.  Otherwise, it is treated as a regular expression and any
     * PatternSyntaxExceptions are caught. If it does not match or if the pattern is invalid, it is tested for string
     * equality with the input.
     *
     * @param inputSelector test string which may be a regular expression, or explicit regular expression string wrapped
     *                      in '/' characters
     * @param item          item to test
     *
     * @return true if the item matches the selector
     */
    public static boolean matchRegexOrEquals(final String inputSelector, final String item) {
        //see if inputSelector is wrapped in '/' chars
        String testregex = inputSelector;
        if (testregex.length() >= 2 && testregex.indexOf('/') == 0
                && testregex.lastIndexOf('/') == testregex.length() - 1) {
            testregex = inputSelector.substring(1, inputSelector.length() - 1);
            return item.matches(testregex.trim());
        }
        boolean match = false;
        try {
            match = item.matches(inputSelector.trim());
        } catch (PatternSyntaxException e) {

        }
        return match || inputSelector.trim().equals(item);
    }

    /**
     * Create a NodeSet from a filter
     * @param filter
     * @return
     */
    public static NodeSet fromFilter(String filter) {
        Map<String, Map<String, String>> stringMapMap = parseFilter(filter);
        NodeSet nodeSet = new NodeSet();
        nodeSet.createInclude(stringMapMap.get("include"));
        nodeSet.createExclude(stringMapMap.get("exclude"));
        return nodeSet;
    }

    /**
     * Parse textual filter and return a map of maps: [include: [key:value,...], exclude: [key:value,...]]
     *
     * @param filter
     *
     * @return
     */
    public static Map<String, Map<String, String>> parseFilter(String filter) {
        return parseFilter(filter, true, DEFAULT_FILTER_KEY);
    }

    public static Map<String, Map<String, String>> parseFilter(String filter, final boolean joinMulti,
            final String defaultKey) {
        Map<String, String> exclude = new HashMap<String, String>();
        Map<String, String> include = new HashMap<String, String>();
        HashMap<String, Map<String, String>> result = new HashMap<String, Map<String, String>>();
        result.put("include", include);
        result.put("exclude", exclude);

        String[] burst = OptsUtil.burst(filter);
        String key = null;
        for (int i = 0; i < burst.length; i++) {
            String t = burst[i];
            if (t.endsWith(":") && key == null) {
                key = t.substring(0, t.lastIndexOf(':'));
                continue;
            } else if (t.contains(":") && key == null) {
                key = t.substring(0, t.indexOf(':'));
                t = t.substring(t.indexOf(':') + 1);
                if ("".equals(t)) {
                    continue;
                }
            } else if (key == null && null != defaultKey) {
                //default filter key
                key = defaultKey;
            }
            if (key != null) {
                Map<String, String> set = include;
                if (key.startsWith("!")) {
                    key = key.substring(1);
                    set = exclude;
                }
                if (null != set.get(key) && joinMulti) {
                    set.put(key, set.get(key) + "," + t);
                } else {
                    set.put(key, t);
                }
                key = null;
            } else {
                key = t;
            }
        }
        return result;
    }

    public static String generateFilter(NodeSet set) {
        ArrayList<String> buf = new ArrayList<String>();
        Map<String, String> incMap = set.includes.toMap();
        for (String inc : incMap.keySet()) {
            buf.add(inc + ":");
            buf.add(incMap.get(inc));
        }
        Map<String, String> excMap = set.excludes.toMap();
        for (String inc : excMap.keySet()) {
            buf.add("!" + inc + ":");
            buf.add(excMap.get(inc));
        }
        return OptsUtil.join(buf);
    }

    /**
     * Creates an {@link Exclude} object populating its
     * properties from the keys of the map
     * @param excludeMap Map containing nodes.properties data
     * @return new Exclude object configured for use in this NodeSet
     */
    public Exclude createExclude(Map excludeMap) {
        Exclude exclude = createExclude();
        populateSetSelector(excludeMap, exclude);
        return exclude;
    }

    /**
     * Creates an {@link Include} object populating its
     * properties from the keys of the map
     * @param includeMap Map containing nodes.properties data
     * @return new Exclude object configured for use in this NodeSet
     */
    public Include createInclude(Map includeMap) {
        Include include = createInclude();
        populateSetSelector(includeMap, include);
        return include;
    }

    /**
     * Creates an {@link SetSelector} object populating its
     * properties from the keys of the map
     *
     * @param map         Map containing nodes.properties data
     * @param setselector include or exclude object
     * @return new SetSelector object configured for use in this NodeSet
     */
    public SetSelector populateSetSelector(Map map, SetSelector setselector) {
        HashMap<String, String> attrs = new HashMap<String, String>();
        for (final Object o : map.entrySet()) {
            final Map.Entry entry = (Map.Entry) o;
            final String key = (String) entry.getKey();
            final String value = asString(entry.getValue(), ",");
            if (HOSTNAME.equals(key)) {
                setselector.setHostname(value);
            } else if (OS_FAMILY.equals(key)) {
                setselector.setOsfamily(value);
            } else if (OS_ARCH.equals(key)) {
                setselector.setOsarch(value);
            } else if (OS_NAME.equals(key)) {
                setselector.setOsname(value);
            } else if (OS_VERSION.equals(key)) {
                setselector.setOsversion(value);
            } else if (NAME.equals(key)) {
                setselector.setName(value);
            } else if (TAGS.equals(key)) {
                setselector.setTags(value);
            } else {
                attrs.put(key, value);
            }
        }
        setselector.setAttributesMap(attrs);
        return setselector;
    }

    private String asString(Object value, String delim) {
        if (null == value) {
            return null;
        }
        if (value instanceof String) {
            return (String) value;
        } else if (value instanceof String[]) {
            String[] s = (String[]) value;
            return StringArrayUtil.asString(s, delim);
        } else if (value instanceof Collection) {
            return StringUtils.join((Collection) value, delim);
        } else {
            return value.toString();
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        NodeSet nodeSet = (NodeSet) o;

        if (keepgoing != nodeSet.keepgoing) {
            return false;
        }
        if (threadCount != nodeSet.threadCount) {
            return false;
        }
        if (excludes != null ? !excludes.equals(nodeSet.excludes) : nodeSet.excludes != null) {
            return false;
        }
        if (includes != null ? !includes.equals(nodeSet.includes) : nodeSet.includes != null) {
            return false;
        }
        if (singleNodeName != null ? !singleNodeName.equals(nodeSet.singleNodeName)
                : nodeSet.singleNodeName != null) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = singleNodeName != null ? singleNodeName.hashCode() : 0;
        result = 31 * result + (includes != null ? includes.hashCode() : 0);
        result = 31 * result + (excludes != null ? excludes.hashCode() : 0);
        result = 31 * result + threadCount;
        result = 31 * result + (keepgoing ? 1 : 0);
        return result;
    }

    public String toString() {
        final StringBuilder builder = new StringBuilder();
        builder.append("NodeSet{");
        if (null != excludes && !excludes.isBlank()) {
            builder.append("excludes=").append(excludes).append(", ");
        }
        if (null != includes && !includes.isBlank()) {
            builder.append("includes=").append(includes);
        }
        if (null != singleNodeName) {
            builder.append("singleNode=").append(singleNodeName);
        }
        builder.append("}");
        return builder.toString();
    }

    public File getFailedNodesfile() {
        return failedNodesfile;
    }

    public void setFailedNodesfile(File failedNodesfile) {
        this.failedNodesfile = failedNodesfile;
    }

    /**
     * SetSelector is a filter
     */
    public abstract class SetSelector {
        private boolean dominant = false;
        private String hostname = "";
        private String osfamily = "";
        private String osarch = "";
        private String osname = "";
        private String tags = "";
        private String osversion = "";
        private String name = "";
        private Map<String, String> attributesMap;

        protected SetSelector() {
            attributesMap = new HashMap<String, String>();
        }

        public void setHostname(String hostname) {
            this.hostname = hostname;
        }

        public String getHostname() {
            return hostname;
        }

        public String toString() {
            final StringBuilder builder = new StringBuilder();
            builder.append("{");
            if (!isBlank(hostname)) {
                builder.append("hostname=").append(getHostname()).append(", ");
            }
            if (!isBlank(osfamily)) {
                builder.append("osfamily=").append(getOsfamily()).append(", ");
            }
            if (!isBlank(osarch)) {
                builder.append("osarch=").append(getOsarch()).append(", ");
            }
            if (!isBlank(osname)) {
                builder.append("osname=").append(getOsname()).append(", ");
            }
            if (!isBlank(osversion)) {
                builder.append("osversion=").append(getOsversion()).append(", ");
            }
            if (!isBlank(tags)) {
                builder.append("tags=").append(getTags()).append(", ");
            }
            if (!isBlank(name)) {
                builder.append("name=").append(getName()).append(", ");
            }
            builder.append("dominant=").append(isDominant()).append(", ");

            if (null != getAttributesMap() && getAttributesMap().size() > 0) {
                builder.append("attributesMap=").append(getAttributesMap());
            }
            builder.append("}");
            return builder.toString();
        }

        public Map<String, String> toMap() {
            HashMap<String, String> map = new HashMap<String, String>();
            if (!isBlank(hostname)) {
                map.put("hostname", getHostname());
            }
            if (!isBlank(osfamily)) {
                map.put("osfamily", getOsfamily());
            }
            if (!isBlank(osarch)) {
                map.put("osarch", getOsarch());
            }
            if (!isBlank(osname)) {
                map.put("osname", getOsname());
            }
            if (!isBlank(osversion)) {
                map.put("osversion", getOsversion());
            }
            if (!isBlank(tags)) {
                map.put("tags", getTags());
            }
            if (!isBlank(name)) {
                map.put("name", getName());
            }
            //            builder.append("dominant=").append(isDominant()).append(", ");

            if (null != getAttributesMap() && getAttributesMap().size() > 0) {
                map.putAll(getAttributesMap());

            }
            return map;
        }

        public boolean isBlank(String value) {
            return (null == value || "".equals(value.trim()));
        }

        public boolean isBlank() {
            return isBlank(hostname) && isBlank(osfamily) && isBlank(osarch) && isBlank(osname) && isBlank(tags)
                    && isBlank(osversion) && isBlank(name) && isBlank(getAttributesMap());
        }

        public boolean isBlank(Map map) {
            return null == map || map.isEmpty();
        }

        public boolean matchOrBlank(Map<String, String> selector, Map<String, String> value) {
            return isBlank(selector) || matchesInput(selector, value, true);
        }

        public boolean matchOrBlank(String selector, String value) {
            return isBlank(selector) || matchesInput(selector, value);
        }

        public boolean matchOrBlank(String selector, Collection set) {
            return isBlank(selector) || matchesInputSet(selector, set);
        }

        public boolean matches(INodeEntry entry) {
            return !isBlank() && matchOrBlank(hostname, entry.getHostname())
                    && matchOrBlank(name, entry.getNodename()) && matchOrBlank(tags, entry.getTags())
                    && matchOrBlank(osfamily, entry.getOsFamily()) && matchOrBlank(osarch, entry.getOsArch())
                    && matchOrBlank(osname, entry.getOsName()) && matchOrBlank(osversion, entry.getOsVersion())
                    && matchOrBlank(getAttributesMap(), entry.getAttributes());
        }

        public String getOsfamily() {
            return osfamily;
        }

        public void setOsfamily(String osfamily) {
            this.osfamily = osfamily;
        }

        public String getOsarch() {
            return osarch;
        }

        public void setOsarch(String osarch) {
            this.osarch = osarch;
        }

        public String getOsname() {
            return osname;
        }

        public void setOsname(String osname) {
            this.osname = osname;
        }

        public String getTags() {
            return tags;
        }

        public void setTags(String tags) {
            this.tags = tags;
        }

        public String getOsversion() {
            return osversion;
        }

        public void setOsversion(String osversion) {
            this.osversion = osversion;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public boolean isDominant() {
            return dominant;
        }

        public void setDominant(boolean dominant) {
            this.dominant = dominant;
        }

        /**
         * Generate a Map of attribute names to values
         * @return
         */
        Map<String, String> getAttributesMap() {
            return attributesMap;
        }

        public void setAttributesMap(Map<String, String> attributesMap) {
            this.attributesMap = attributesMap;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof SetSelector)) {
                return false;
            }

            SetSelector that = (SetSelector) o;

            if (dominant != that.dominant) {
                return false;
            }
            if (attributesMap != null ? !attributesMap.equals(that.attributesMap) : that.attributesMap != null) {
                return false;
            }
            if (hostname != null ? !hostname.equals(that.hostname) : that.hostname != null) {
                return false;
            }
            if (name != null ? !name.equals(that.name) : that.name != null) {
                return false;
            }
            if (osarch != null ? !osarch.equals(that.osarch) : that.osarch != null) {
                return false;
            }
            if (osfamily != null ? !osfamily.equals(that.osfamily) : that.osfamily != null) {
                return false;
            }
            if (osname != null ? !osname.equals(that.osname) : that.osname != null) {
                return false;
            }
            if (osversion != null ? !osversion.equals(that.osversion) : that.osversion != null) {
                return false;
            }
            if (tags != null ? !tags.equals(that.tags) : that.tags != null) {
                return false;
            }

            return true;
        }

        @Override
        public int hashCode() {
            int result = (dominant ? 1 : 0);
            result = 31 * result + (hostname != null ? hostname.hashCode() : 0);
            result = 31 * result + (osfamily != null ? osfamily.hashCode() : 0);
            result = 31 * result + (osarch != null ? osarch.hashCode() : 0);
            result = 31 * result + (osname != null ? osname.hashCode() : 0);
            result = 31 * result + (tags != null ? tags.hashCode() : 0);
            result = 31 * result + (osversion != null ? osversion.hashCode() : 0);
            result = 31 * result + (name != null ? name.hashCode() : 0);
            result = 31 * result + (attributesMap != null ? attributesMap.hashCode() : 0);
            return result;
        }
    }

    public class Include extends SetSelector {

    }

    public class Exclude extends SetSelector {
    }
}