001package org.biopax.paxtools.pattern;
002
003import org.biopax.paxtools.model.BioPAXElement;
004
005import java.util.*;
006
007/**
008 * A pattern is a list of mapped constraints.
009 *
010 * @author Ozgun Babur
011 */
012public class Pattern
013{
014        /**
015         * How many elements are there in a pattern match.
016         */
017        protected int lastIndex;
018
019        /**
020         * Class of the first elements to match with this pattern.
021         */
022        protected Class<? extends BioPAXElement> startingClass;
023
024        /**
025         * List of mapped constraints.
026         */
027        protected List<MappedConst> constraints;
028
029        /**
030         * Indexes in a pattern can be labeled using this map.
031         */
032        protected Map<String, Integer> labelMap;
033
034        /**
035         * Constructor with the starting class.
036         * @param startingClass class of first element in the pattern
037         */
038        private Pattern(Class<? extends BioPAXElement> startingClass)
039        {
040                this.startingClass = startingClass;
041                this.labelMap = new HashMap<String, Integer>();
042                this.constraints = new ArrayList<MappedConst>();
043                this.lastIndex = 0;
044        }
045
046        /**
047         * Constructor with the first constraint and labels it uses. This constructor is good for
048         * patterns with a single constraint.
049         * @param startingClass type of initial element
050         * @param firstConstraint first constraint
051         * @param label labels for the constraint
052         */
053        public Pattern(Class<? extends BioPAXElement> startingClass, Constraint firstConstraint,
054                String... label)
055        {
056                this(startingClass, label[0]);
057                add(firstConstraint, label);
058        }
059
060        /**
061         * Constructor with a label for the element at index 0.
062         * @param startingClass type of the initial element
063         * @param label a label for the initial element
064         */
065        public Pattern(Class<? extends BioPAXElement> startingClass, String label)
066        {
067                this(startingClass);
068                label(label, 0);
069        }
070
071        /**
072         * Creates a mapped constraint with the given constraint and the indexes it applies.
073         * @param constr constraint to add
074         * @param ind indices to map the constraint to the element in the pattern
075         */
076        private void add(Constraint constr, int... ind)
077        {
078                assert ind.length > 0;
079                assert constr.getVariableSize() == ind.length;
080
081                for (int i = 0; i < (constr.canGenerate() ? ind.length - 1 : ind.length); i++)
082                {
083                        assert ind[i] <= lastIndex;
084                }
085
086                constraints.add(new MappedConst(constr, ind));
087
088                if (constr.canGenerate() && ind[ind.length - 1] > lastIndex)
089                {
090                        if (ind[ind.length - 1] - lastIndex > 1) throw new IllegalArgumentException(
091                                "Generated index too large. Attempting to generate index " + ind[ind.length - 1] +
092                                        " while last index is " + lastIndex);
093
094                        else lastIndex++;
095                }
096        }
097
098        /**
099         * Removes the last constraint added to the pattern.
100         */
101        public void removeLastConstraint()
102        {
103                if (constraints.isEmpty()) return;
104
105                MappedConst mc = constraints.get(constraints.size() - 1);
106                constraints.remove(mc);
107                if (mc.canGenerate() && mc.getInds()[mc.getInds().length - 1] == lastIndex)
108                {
109                        setLastIndexToMaxFound();
110                }
111        }
112
113        /**
114         * This method is used after removal of an generative constraint. Searching the remaining
115         * constraints, we decide the value of lastIndex.
116         */
117        private void setLastIndexToMaxFound()
118        {
119                int max = -1;
120                for (MappedConst mc : constraints)
121                {
122                        int[] ind = mc.getInds();
123                        int last = ind[ind.length - 1];
124                        if (last > max) max = last;
125                }
126                lastIndex = max;
127        }
128
129        public void optimizeConstraintOrder()
130        {
131                // determine what should come after what
132
133                Map<MappedConst, Set<MappedConst>> preMap = new HashMap<MappedConst, Set<MappedConst>>();
134
135                for (MappedConst con1 : constraints)
136                {
137                        preMap.put(con1, new HashSet<MappedConst>());
138
139                        int m1 = con1.getMaxInd();
140                        for (MappedConst con2 : constraints)
141                        {
142                                if (con1 == con2) continue;
143
144                                int m2 = con2.getMaxInd();
145
146                                if (m1 > m2 || (m1 == m2 && con2.canGenerate() && !con1.canGenerate()))
147                                {
148                                        preMap.get(con1).add(con2);
149                                }
150                        }
151                }
152
153                // determine ordered levels
154
155                List<Set<MappedConst>> levels = new ArrayList<Set<MappedConst>>();
156
157                do
158                {
159                        Set<MappedConst> level = new HashSet<MappedConst>();
160
161                        for (MappedConst con : preMap.keySet())
162                        {
163                                if (preMap.get(con).isEmpty()) level.add(con);
164                        }
165
166                        for (MappedConst con : level)
167                        {
168                                preMap.remove(con);
169                        }
170
171                        for (MappedConst con : preMap.keySet())
172                        {
173                                preMap.get(con).removeAll(level);
174                        }
175
176                        levels.add(level);
177                }
178                while (!preMap.isEmpty());
179
180                // build new constraints list
181
182                List<MappedConst> newList = new ArrayList<MappedConst>(constraints.size());
183
184                for (Set<MappedConst> level : levels)
185                {
186                        List<MappedConst> temp = new ArrayList<MappedConst>(constraints);
187                        temp.retainAll(level);
188                        newList.addAll(temp);
189                }
190
191                this.constraints = newList;
192        }
193
194        /**
195         * Creates a mapped constraint with the given generative constraint and the indexes it applies.
196         * Also labels the last given index.
197         * @param constr constraint to add
198         * @param label a label for the last of the given indices
199         */
200        public void add(Constraint constr, String... label)
201        {
202                checkLabels(constr.canGenerate(), label);
203
204                int[] ind = convertLabelsToInds(label);
205
206                if (ind.length != constr.getVariableSize())
207                {
208                        throw new IllegalArgumentException("Mapped elements do not match the constraint size.");
209                }
210
211                // This will also increment lastIndex if necessary
212                add(constr, ind);
213
214                if (!hasLabel(label[label.length - 1]) && constr.canGenerate())
215                {
216                        label(label[label.length - 1], lastIndex);
217                }
218        }
219
220        /**
221         * Converts the labels to the indexes. Assumes the sanity of the labels already checked and if
222         * any new label exists, it is only one and it is the last one.
223         * @param label labels
224         * @return indexes for labels
225         */
226        private int[] convertLabelsToInds(String... label)
227        {
228                int[] ind = new int[label.length];
229                for (int i = 0; i < label.length; i++)
230                {
231                        if (hasLabel(label[i]))
232                        {
233                                ind[i] = indexOf(label[i]);
234                        }
235                        else ind[i] = lastIndex + 1;
236                }
237                return ind;
238        }
239
240        /**
241         * Converts the indices to the labels. All indices must have an existing label.
242         * @param ind indices
243         * @return labels for indices
244         */
245        private String[] convertIndsToLabels(int... ind)
246        {
247                String[] label = new String[ind.length];
248                for (int i = 0; i < ind.length; i++)
249                {
250                        if (!hasLabel(ind[i])) throw new IllegalArgumentException(
251                                "The index " + ind[i] + " does not have a label.");
252
253                        label[i] = getLabel(ind[i]);
254                }
255                return label;
256        }
257
258        /**
259         * Checks if all labels (other than the last if generative) exists.
260         * @param forGenerative whether the check is performed for a generative constraint
261         * @param label labels to check
262         * @throws IllegalArgumentException when a necessary label is not found
263         */
264        private void checkLabels(boolean forGenerative, String... label)
265        {
266                for (int i = 0; i < (forGenerative ? label.length - 1 : label.length); i++)
267                {
268                        if (!hasLabel(label[i])) throw new IllegalArgumentException(
269                                "Label neither found, nor generated: " + label[i]);
270                }
271        }
272
273        /**
274         * Appends the constraints in the parameter pattern to the desired location. Indexes in the
275         * constraint mappings are translated so that 0 is translated to ind0, and others are translated
276         * to orig + indAppend - 1. All slots of this pattern should already be full before calling this
277         * method. This method makes room for the new variables. Labels in the parameter pattern is
278         * transferred to this pattern. If there are equivalent labels, then these slots are mapped.
279         * 
280         * @param p the parameter pattern
281         */
282        public void add(Pattern p)
283        {
284                if (!hasLabel(p.getLabel(0))) throw new IllegalArgumentException("The label of first " +
285                        "element of parameter index \"" + p.getLabel(0) + "\" not found in this pattern.");
286
287                for (MappedConst mc : p.getConstraints())
288                {
289                        add(mc.getConstr(), p.convertIndsToLabels(mc.getInds()));
290                }
291        }
292
293        /**
294         * Gets the label for the element at the specified index.
295         * @param i index
296         * @return label for the element at the specified index
297         */
298        private String getLabel(int i)
299        {
300                for (String label : labelMap.keySet())
301                {
302                        if (labelMap.get(label) == i) return label;
303                }
304                return null;
305        }
306
307        /**
308         * A point constraint deals with only one element in a match, checks its validity. This method
309         * injects the parameter constraint multiple times among the list of mapped constraints, to the
310         * specified indexes.
311         *
312         * @param con constraint to add
313         * @param ind indices to add this point constraint
314         */
315        public void insertPointConstraint(Constraint con, int ... ind)
316        {
317                assert con.getVariableSize() == 1;
318
319                for (int i : ind)
320                {
321                        for (int j = 0; j < constraints.size(); j++)
322                        {
323                                int[] index = constraints.get(j).getInds();
324                                if (index[index.length-1] == i)
325                                {
326                                        constraints.add(j + 1, new MappedConst(con, i));
327                                        break;
328                                }
329                        }
330                }
331        }
332
333        /**
334         * Getter for the constraint list.
335         * @return constraints
336         */
337        public List<MappedConst> getConstraints()
338        {
339                return constraints;
340        }
341
342        /**
343         * Gets the element size of the pattern.
344         * @return size inferred from lastIndex
345         */
346        public int size()
347        {
348                return lastIndex + 1;
349        }
350
351        /**
352         * Gets the type of the initial element.
353         * @return type of first element in a match
354         */
355        public Class<? extends BioPAXElement> getStartingClass()
356        {
357                return startingClass;
358        }
359
360        /**
361         * Puts the given label for the given index.
362         * @param labelText the label
363         * @param index index to label
364         */
365        public void label(String labelText, int index)
366        {
367                if (labelMap.containsKey(labelText)) throw new IllegalArgumentException(
368                        "Label \"" + labelText + "\" already exists.");
369
370                if (labelMap.containsValue(index)) throw new IllegalArgumentException(
371                        "Index \"" + index + "\" already has a label.");
372
373                labelMap.put(labelText, index);
374        }
375
376        /**
377         * Checks if the label is already in use.
378         * @param labelText label to check
379         * @return true if label exists
380         */
381        public boolean hasLabel(String labelText)
382        {
383                return labelMap.containsKey(labelText);
384        }
385
386        /**
387         * Checks if the given location has a label.
388         * @param index index to check
389         * @return true if a label exists for the given index
390         */
391        public boolean hasLabel(int index)
392        {
393                return labelMap.containsValue(index);
394        }
395
396        /**
397         * Gets the index of the given label. The label must exist, otherwise a runtime exception is
398         * thrown.
399         * @param labelText label to check
400         * @return index of the label
401         */
402        public int indexOf(String labelText)
403        {
404                if (!labelMap.containsKey(labelText))
405                        throw new IllegalArgumentException("The label \"" + labelText +
406                                "\" is absent in pattern.");
407                
408                return labelMap.get(labelText);
409        }
410
411        /**
412         * Changes a label. The oldLabel has to be an existing label and new label has to be a new
413         * label.
414         * @param oldLabel label to update
415         * @param newLabel updated label
416         */
417        public void updateLabel(String oldLabel, String newLabel)
418        {
419                if (hasLabel(newLabel)) throw new IllegalArgumentException(
420                        "The label \"" + newLabel + "\" already exists.");
421
422                int i = indexOf(oldLabel);
423                labelMap.remove(oldLabel);
424                labelMap.put(newLabel, i);
425        }
426}