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