001package org.biopax.paxtools.pattern.constraint;
002
003import org.biopax.paxtools.model.BioPAXElement;
004import org.biopax.paxtools.pattern.MappedConst;
005import org.biopax.paxtools.pattern.Match;
006
007import java.util.Collection;
008import java.util.HashSet;
009import java.util.Set;
010
011/**
012 * Checks if generated elements has or has not a specific value for a field, or the field value of
013 * another set of generated elements.
014 *
015 * @author Ozgun Babur
016 */
017public class FieldOfMultiple extends Field
018{
019        /**
020         * Generative constraint for first group of generated elements, to use on first mapped element.
021         */
022        protected MappedConst con1;
023
024        /**
025         * Generative constraint for second group of generated elements, to use on second mapped
026         * element.
027         */
028        protected MappedConst con2;
029
030        /**
031         * Constructor with accessor string for the field value of the element and the desired value. If
032         * the desired value is EMPTY, then emptiness is checked. If it is USE_SECOND_ARG, then the
033         * second mapped element is used as the desired value. If a filed of the second element is
034         * desired then the other constructor should be used.
035         *
036         * @param con the generative constraint of size 2 whose generated values will be checked
037         * @param accessorString accessor string for the element
038         * @param oper operation
039         * @param value desired value
040         */
041        public FieldOfMultiple(MappedConst con, String accessorString, Operation oper, Object value)
042        {
043                super(accessorString, oper, value);
044                con1 = con;
045        }
046
047        /**
048         * Constructor with accessor strings for the field value of the element and the desired value
049         * that will be reached from the second element.
050         *
051         * @param con1 the generative constraint of size 2 for the first group of elements
052         * @param accessorString1 accessor string for the first element
053         * @param con2 the generative constraint of size 2 for the second group of elements
054         * @param accessorString2 accessor string for the second element
055         * @param oper operation
056         */
057        public FieldOfMultiple(MappedConst con1, String accessorString1, MappedConst con2,
058                String accessorString2, Operation oper)
059        {
060                super(accessorString1, accessorString2, oper);
061                this.con1 = con1;
062                this.con2 = con2;
063        }
064
065        /**
066         * Size of this constraint is one less than con1 if con2 is null, otherwise it is two less than
067         * the total of size of con1 and con2.
068         * @return the size based on con1 and con2
069         */
070        @Override
071        public int getVariableSize()
072        {
073                return con1.getVariableSize() + (con2 != null ? con2.getVariableSize() - 2 : -1);
074        }
075
076        /**
077         * Checks if the generated elements from the first mapped element has either the desired value,
078         * or has some value in common with the elements generated from second mapped element.
079         * @param match current pattern match
080         * @param ind mapped indices
081         * @return true if a value match is found
082         */
083        @Override
084        public boolean satisfies(Match match, int... ind)
085        {
086                assertIndLength(ind);
087
088                // Collect values of the element group
089                Set values = new HashSet();
090                for (BioPAXElement gen : con1.generate(match, ind))
091                {
092                        values.addAll(pa1.getValueFromBean(gen));
093                }
094
095                // If emptiness is desired, check that
096                if (value == EMPTY) return values.isEmpty();
097
098                // If cannot be empty, check it
099                if (oper == Operation.NOT_EMPTY_AND_NOT_INTERSECT && values.isEmpty()) return false;
100
101                // If the second element is desired value, check that
102                else if (value == USE_SECOND_ARG)
103                {
104                        BioPAXElement q = match.get(ind[1]);
105                        return oper == Operation.INTERSECT ? values.contains(q) : !values.contains(q);
106                }
107
108                // If element group is compared to preset value, but the value is actually a collection,
109                // then iterate the collection, see if any of them matches
110                else if (value instanceof Collection)
111                {
112                        Collection query = (Collection) value;
113                        values.retainAll(query);
114
115                        if (oper == Operation.INTERSECT) return !values.isEmpty();
116                        else return values.isEmpty();
117                }
118
119                // If two set of elements should share a field value, check that
120                else if (pa2 != null)
121                {
122                        // Collect values of the second group
123                        Set others = new HashSet();
124                        for (BioPAXElement gen : con2.generate(match, ind))
125                        {
126                                others.addAll(pa2.getValueFromBean(gen));
127                        }
128
129                        switch (oper)
130                        {
131                                case INTERSECT:
132                                        others.retainAll(values);
133                                        return !others.isEmpty();
134                                case NOT_INTERSECT:
135                                        others.retainAll(values);
136                                        return others.isEmpty();
137                                case NOT_EMPTY_AND_NOT_INTERSECT:
138                                        if (others.isEmpty()) return false;
139                                        others.retainAll(values);
140                                        return others.isEmpty();
141                                default: throw new RuntimeException("Unhandled operation: " + oper);
142                        }
143                }
144
145                // Check if the element field values contain the parameter value
146                else if (oper == Operation.INTERSECT) return values.contains(value);
147                else return !values.contains(value);
148        }
149}