001package org.biopax.paxtools.pattern.constraint;
002
003import org.biopax.paxtools.model.level3.*;
004import org.biopax.paxtools.model.level3.Process;
005import org.biopax.paxtools.pattern.Constraint;
006import org.biopax.paxtools.pattern.Match;
007import org.biopax.paxtools.model.BioPAXElement;
008import org.biopax.paxtools.pattern.util.Blacklist;
009import org.biopax.paxtools.pattern.util.RelType;
010
011import java.util.*;
012
013/**
014 * This is a base class for a Constraint. Most constraints should typically extend this class.
015 *
016 * @author Ozgun Babur
017 */
018public abstract class ConstraintAdapter implements Constraint
019{
020        /**
021         * Size of the constraint. This is defined by how many elements this constraint needs to be \
022         * mapped to work.
023         */
024        protected int size;
025
026        /**
027         * Blacklist to detect ubiquitous small molecules.
028         */
029        protected Blacklist blacklist;
030
031        /**
032         * Constructor with size.
033         * @param size size if the constraint.
034         */
035        protected ConstraintAdapter(int size)
036        {
037                this.size = size;
038        }
039
040        /**
041         * Constructor with size.
042         * @param size size if the constraint.
043         * @param blacklist for detecting ubiquitous small molecules
044         */
045        protected ConstraintAdapter(int size, Blacklist blacklist)
046        {
047                this.size = size;
048                this.blacklist = blacklist;
049        }
050
051        /**
052         * Empty constructor. Note that specifying the size is not mandatory since the child constraint
053         * can override <code>getVariableSize</code> instead of using the size variable.
054         */
055        protected ConstraintAdapter()
056        {
057        }
058
059        /**
060         * Specifies if the constraint is generative. If you override this method, then don't forget to
061         * also override getGeneratedInd, and generate methods.
062         */
063        @Override
064        public boolean canGenerate()
065        {
066                return false;
067        }
068
069        /**
070         * This method has to be overridden by generative constraints.
071         *
072         * @param match current pattern match
073         * @param ind mapped indices
074         * @return elements that satisfy this constraint
075         */
076        @Override
077        public Collection<BioPAXElement> generate(Match match, int ... ind)
078        {
079                throw new RuntimeException("This constraint is not generative. " +
080                        "Please check with canGenerate first.");
081        }
082
083        /**
084         * Use this method only if constraint canGenerate, and satisfaction criteria is that simple.
085         *
086         * @param match current pattern match
087         * @param ind mapped indices
088         * @return true if the match satisfies this constraint
089         */
090        @Override
091        public boolean satisfies(Match match, int... ind)
092        {
093                return generate(match, ind).contains(match.get(ind[ind.length - 1]));
094        }
095
096        /**
097         * Asserts the size of teh parameter array is equal to the variable size.
098         * @param ind index array to assert its size
099         */
100        protected void assertIndLength(int[] ind)
101        {
102                assert ind.length == getVariableSize();
103        }
104
105        /**
106         * Sets the size of the constraint.
107         * @param size size of the constraint
108         */
109        public void setSize(int size)
110        {
111                this.size = size;
112        }
113
114        /**
115         * Gets the variable size of the constraint.
116         * @return variable size
117         */
118        @Override
119        public int getVariableSize()
120        {
121                return size;
122        }
123
124        //----- Section: Common BioPAX Operations -----------------------------------------------------|
125
126        /**
127         * Gets the direction of the Control chain the the Interaction.
128         * @param conv controlled conversion
129         * @param cont top control
130         * @return the direction of the conversion related to the catalysis
131         */
132        protected ConversionDirectionType getDirection(Conversion conv, Control cont)
133        {
134                return getDirection(conv, null, cont);
135        }
136
137        /**
138         * Gets the direction of the Control chain the the Interaction.
139         * @param conv controlled conversion
140         * @param pathway the container pathway
141         * @param cont top control
142         * @return the direction of the conversion related to the catalysis
143         */
144        protected ConversionDirectionType getDirection(Conversion conv, Pathway pathway,  Control cont)
145        {
146                for (Control ctrl : getControlChain(cont, conv))
147                {
148                        ConversionDirectionType dir = getCatalysisDirection(ctrl);
149                        if (dir != null) return dir;
150                }
151
152                Set<StepDirection> dirs = new HashSet<StepDirection>();
153
154                Set<PathwayStep> convSteps = conv.getStepProcessOf();
155
156                // maybe the direction is embedded in a pathway step
157                for (PathwayStep step : cont.getStepProcessOf())
158                {
159                        if (pathway != null && !step.getPathwayOrderOf().equals(pathway)) continue;
160
161                        if (step instanceof BiochemicalPathwayStep && convSteps.contains(step))
162                        {
163                                StepDirection dir = ((BiochemicalPathwayStep) step).getStepDirection();
164                                if (dir != null) dirs.add(dir);
165                        }
166                }
167
168                if (dirs.size() > 1) return ConversionDirectionType.REVERSIBLE;
169                else if (!dirs.isEmpty()) return convertStepDirection(dirs.iterator().next());
170
171                return getDirection(conv);
172        }
173
174        /**
175         * Gets the direction of the Control chain the the Interaction.
176         * @param conv controlled conversion
177         * @param pathway the container pathway
178         * @return the direction of the conversion related to the catalysis
179         */
180        protected ConversionDirectionType getDirection(Conversion conv, Pathway pathway)
181        {
182                Set<StepDirection> dirs = new HashSet<StepDirection>();
183
184                // find the direction in the pathway step
185                for (PathwayStep step : conv.getStepProcessOf())
186                {
187                        if (step.getPathwayOrderOf().equals(pathway) && step instanceof BiochemicalPathwayStep)
188                        {
189                                StepDirection dir = ((BiochemicalPathwayStep) step).getStepDirection();
190                                if (dir != null) dirs.add(dir);
191                        }
192                }
193
194                if (dirs.size() > 1) return ConversionDirectionType.REVERSIBLE;
195                else if (!dirs.isEmpty()) return convertStepDirection(dirs.iterator().next());
196
197                return getDirection(conv);
198        }
199
200        /**
201         * Gets the direction of the Control, if exists.
202         * @param cont Control to get its direction
203         * @return the direction of the Control
204         */
205        protected ConversionDirectionType getCatalysisDirection(Control cont)
206        {
207                if (cont instanceof Catalysis)
208                {
209                        CatalysisDirectionType catDir = ((Catalysis) cont).getCatalysisDirection();
210
211                        if (catDir == CatalysisDirectionType.LEFT_TO_RIGHT)
212                        {
213                                return ConversionDirectionType.LEFT_TO_RIGHT;
214                        }
215                        else if (catDir == CatalysisDirectionType.RIGHT_TO_LEFT)
216                        {
217                                return ConversionDirectionType.RIGHT_TO_LEFT;
218                        }
219                }
220                return null;
221        }
222
223        /**
224         * Gets the chain of Control, staring from the given Control, leading to the given Interaction.
225         * Use this method only if you are sure that there is a link from the control to conversion.
226         * Otherwise a RuntimeException is thrown. This assumes that there is only one control chain
227         * towards the interaction. It not, then one of the chains will be returned.
228         *
229         * @param control top level Control
230         * @param inter target Interaction
231         * @return Control chain controlling the Interaction
232         */
233        protected List<Control> getControlChain(Control control, Interaction inter)
234        {
235                LinkedList<Control> list = new LinkedList<Control>();
236                list.add(control);
237
238                boolean found = search(list, inter);
239
240                if (!found) throw new RuntimeException("No link from Control to Conversion.");
241
242                return list;
243        }
244
245        /**
246         * Checks if the control chain is actually controlling the Interaction.
247         * @param list the Control chain
248         * @param inter target Interaction
249         * @return true if the chain controls the Interaction
250         */
251        private boolean search(LinkedList<Control> list, Interaction inter)
252        {
253                if (list.getLast().getControlled().contains(inter)) return true;
254
255                for (Process process : list.getLast().getControlled())
256                {
257                        if (process instanceof Control)
258                        {
259                                // prevent searching in cycles
260                                if (list.contains(process)) continue;
261
262                                list.add((Control) process);
263                                if (search(list, inter)) return true;
264                                else list.removeLast();
265                        }
266                }
267                return false;
268        }
269
270        /**
271         * Gets input ot output participants of the Conversion.
272         * @param conv Conversion to get participants
273         * @param type input or output
274         * @return related participants
275         */
276        protected Set<PhysicalEntity> getConvParticipants(Conversion conv, RelType type)
277        {
278                ConversionDirectionType dir = getDirection(conv);
279
280                if (dir == ConversionDirectionType.REVERSIBLE)
281                {
282                        HashSet<PhysicalEntity> set = new HashSet<PhysicalEntity>(conv.getLeft());
283                        set.addAll(conv.getRight());
284                        return set;
285                }
286                else if (dir == ConversionDirectionType.RIGHT_TO_LEFT)
287                {
288                        return type == RelType.INPUT ? conv.getRight() : conv.getLeft();
289                }
290                else return type == RelType.OUTPUT ? conv.getRight() : conv.getLeft();
291        }
292
293        /**
294         * Searches pathways that contains this conversion for the possible directions. If both
295         * directions exist, then the result is reversible.
296         * @param conv the conversion
297         * @return direction inferred from pathway membership
298         */
299        protected ConversionDirectionType findDirectionInPathways(Conversion conv)
300        {
301                Set<StepDirection> dirs = new HashSet<StepDirection>();
302                for (PathwayStep step : conv.getStepProcessOf())
303                {
304                        if (step instanceof BiochemicalPathwayStep)
305                        {
306                                StepDirection dir = ((BiochemicalPathwayStep) step).getStepDirection();
307                                if (dir != null) dirs.add(dir);
308                        }
309                }
310                if (dirs.size() > 1) return ConversionDirectionType.REVERSIBLE;
311                else if (!dirs.isEmpty())
312                {
313                        return dirs.iterator().next() == StepDirection.LEFT_TO_RIGHT ?
314                                ConversionDirectionType.LEFT_TO_RIGHT : ConversionDirectionType.RIGHT_TO_LEFT;
315                }
316                else return null;
317        }
318
319        protected ConversionDirectionType convertStepDirection(StepDirection sdir)
320        {
321                if (sdir == StepDirection.LEFT_TO_RIGHT) return ConversionDirectionType.LEFT_TO_RIGHT;
322                if (sdir == StepDirection.RIGHT_TO_LEFT) return ConversionDirectionType.RIGHT_TO_LEFT;
323                return null;
324        }
325
326        /**
327         * Searches the controlling catalysis for possible direction of the conversion.
328         * @param conv the conversion
329         * @return direction inferred from catalysis objects
330         */
331        protected ConversionDirectionType findDirectionInCatalysis(Conversion conv)
332        {
333                Set<ConversionDirectionType> dirs = new HashSet<ConversionDirectionType>();
334                for (Control control : conv.getControlledOf())
335                {
336                        ConversionDirectionType dir = getCatalysisDirection(control);
337                        if (dir != null) dirs.add(dir);
338                }
339                if (dirs.size() > 1) return ConversionDirectionType.REVERSIBLE;
340                else if (!dirs.isEmpty()) return dirs.iterator().next();
341                else return null;
342        }
343
344        protected ConversionDirectionType getDirection(Conversion conv)
345        {
346                if (conv.getConversionDirection() != null) return conv.getConversionDirection();
347
348                ConversionDirectionType catDir = findDirectionInCatalysis(conv);
349                ConversionDirectionType patDir = findDirectionInPathways(conv);
350
351                if (catDir != null && patDir != null && catDir != patDir)
352                        return ConversionDirectionType.REVERSIBLE;
353                else if (catDir != null) return catDir;
354                else if (patDir != null) return patDir;
355
356                // No direction found! Assuming it is left-to-right.
357                else return ConversionDirectionType.LEFT_TO_RIGHT;
358        }
359}