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}