001package org.cpsolver.ifs.extension; 002 003import java.util.ArrayList; 004import java.util.Collection; 005import java.util.HashSet; 006import java.util.HashMap; 007import java.util.Iterator; 008import java.util.LinkedList; 009import java.util.List; 010import java.util.Map; 011import java.util.Queue; 012import java.util.Set; 013 014import org.cpsolver.ifs.assignment.Assignment; 015import org.cpsolver.ifs.assignment.context.AssignmentContext; 016import org.cpsolver.ifs.assignment.context.ExtensionWithContext; 017import org.cpsolver.ifs.model.Constraint; 018import org.cpsolver.ifs.model.Value; 019import org.cpsolver.ifs.model.Variable; 020import org.cpsolver.ifs.solver.Solver; 021import org.cpsolver.ifs.util.DataProperties; 022import org.cpsolver.ifs.util.Progress; 023 024 025/** 026 * MAC propagation. <br> 027 * <br> 028 * During the arc consistency maintenance, when a value is deleted from a 029 * variable's domain, the reason (forming an explanation) can be computed and 030 * attached to the deleted value. Once a variable (say Vx with the assigned 031 * value vx) is unassigned during the search, all deleted values which contain a 032 * pair Vx = vx in their explanations need to be recomputed. Such value can be 033 * either still inconsistent with the current (partial) solution (a different 034 * explanation is attached to it in this case) or it can be returned back to its 035 * variable's domain. Arc consistency is maintained after each iteration step, 036 * i.e., the selected assignment is propagated into the not yet assigned 037 * variables. When a value vx is assigned to a variable Vx, an explanation Vx != 038 * vx' ← Vx = vx is attached to all values vx' of the variable Vx, 039 * different from vx. <br> 040 * <br> 041 * In the case of forward checking (only constraints going from assigned 042 * variables to unassigned variables are revised), computing explanations is 043 * rather easy. A value vx is deleted from the domain of the variable Vx only if 044 * there is a constraint which prohibits the assignment Vx=vx because of the 045 * existing assignments (e.g., Vy = vy, Vz = vz). An explanation for the 046 * deletion of this value vx is then Vx != vx ← (Vy = vy & ... Vz = vz), 047 * where Vy = vy & ... Vz = vz are assignments contained in the prohibiting 048 * constraint. In case of arc consistency, a value vx is deleted from the domain 049 * of the variable Vx if there is a constraint which does not permit the 050 * assignment Vx = vx with other possible assignments of the other variables in 051 * the constraint. This means that there is no support value (or combination of 052 * values) for the value vx of the variable Vx in the constraint. An explanation 053 * is then a union of explanations of all possible support values for the 054 * assignment Vx = vx of this constraint which were deleted. The reason is that 055 * if one of these support values is returned to its variable's domain, this 056 * value vx may be returned as well (i.e., the reason for its deletion has 057 * vanished, a new reason needs to be computed). <br> 058 * <br> 059 * As for the implementation, we only need to enforce arc consistency of the 060 * initial solution and to extend unassign and assign methods. Procedure 061 * {@link MacPropagation#afterAssigned(Assignment, long, Value)} enforces arc consistency of 062 * the solution with the selected assignment variable = value and the procedure 063 * {@link MacPropagation#afterUnassigned(Assignment, long, Value)} "undoes" the assignment 064 * variable = value. It means that explanations of all values which were deleted 065 * and which contain assignment variable = value in their explanations need to 066 * be recomputed. This can be done via returning all these values into their 067 * variables' domains followed by arc consistency maintenance over their 068 * variables. <br> 069 * <br> 070 * Parameters: 071 * <table border='1' summary='Related Solver Parameters'> 072 * <tr> 073 * <th>Parameter</th> 074 * <th>Type</th> 075 * <th>Comment</th> 076 * </tr> 077 * <tr> 078 * <td>MacPropagation.JustForwardCheck</td> 079 * <td>{@link Boolean}</td> 080 * <td>If true, only forward checking instead of full arc consistency is 081 * maintained during the search.</td> 082 * </tr> 083 * </table> 084 * 085 * @version IFS 1.3 (Iterative Forward Search)<br> 086 * Copyright (C) 2006 - 2014 Tomáš Müller<br> 087 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 088 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 089 * <br> 090 * This library is free software; you can redistribute it and/or modify 091 * it under the terms of the GNU Lesser General Public License as 092 * published by the Free Software Foundation; either version 3 of the 093 * License, or (at your option) any later version. <br> 094 * <br> 095 * This library is distributed in the hope that it will be useful, but 096 * WITHOUT ANY WARRANTY; without even the implied warranty of 097 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 098 * Lesser General Public License for more details. <br> 099 * <br> 100 * You should have received a copy of the GNU Lesser General Public 101 * License along with this library; if not see 102 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 103 * @param <V> Variable 104 * @param <T> Value 105 */ 106public class MacPropagation<V extends Variable<V, T>, T extends Value<V, T>> extends ExtensionWithContext<V, T, MacPropagation<V, T>.NoGood> { 107 private static org.apache.logging.log4j.Logger sLogger = org.apache.logging.log4j.LogManager.getLogger(MacPropagation.class); 108 private boolean iJustForwardCheck = false; 109 private Progress iProgress; 110 111 /** List of constraints on which arc-consistency is to be maintained */ 112 protected List<Constraint<V, T>> iConstraints = null; 113 /** Current iteration */ 114 protected long iIteration = 0; 115 116 /** Constructor 117 * @param solver current solver 118 * @param properties solver configuration 119 **/ 120 public MacPropagation(Solver<V, T> solver, DataProperties properties) { 121 super(solver, properties); 122 iJustForwardCheck = properties.getPropertyBoolean("MacPropagation.JustForwardCheck", false); 123 } 124 125 /** Adds a constraint on which arc-consistency is to be maintained 126 * @param constraint a hard constraint 127 **/ 128 public void addConstraint(Constraint<V, T> constraint) { 129 if (iConstraints == null) 130 iConstraints = new ArrayList<Constraint<V, T>>(); 131 iConstraints.add(constraint); 132 } 133 134 /** 135 * Returns true, if arc-consistency is to be maintained on the given 136 * constraint 137 * @param constraint a constraint 138 * @return true if in the set 139 */ 140 public boolean contains(Constraint<V, T> constraint) { 141 if (iConstraints == null) 142 return true; 143 return iConstraints.contains(constraint); 144 } 145 146 /** 147 * Before a value is unassigned: until the value is inconsistent with the 148 * current solution, an assignment from its explanation is picked and 149 * unassigned. 150 */ 151 @Override 152 public void beforeAssigned(Assignment<V, T> assignment, long iteration, T value) { 153 iIteration = iteration; 154 if (value == null) 155 return; 156 if (!isGood(assignment, value)) { 157 while (!isGood(assignment, value) && !noGood(assignment, value).isEmpty()) { 158 T noGoodValue = noGood(assignment, value).iterator().next(); 159 assignment.unassign(iteration, noGoodValue.variable()); 160 } 161 } 162 if (!isGood(assignment, value)) { 163 sLogger.warn("Going to assign a bad value " + value + " with empty no-good."); 164 } 165 } 166 167 /** 168 * After a value is assigned: explanations of other values of the value's 169 * variable are reset (to contain only the assigned value), propagation over 170 * the assigned variable takes place. 171 */ 172 @Override 173 public void afterAssigned(Assignment<V, T> assignment, long iteration, T value) { 174 iIteration = iteration; 175 if (!isGood(assignment, value)) { 176 sLogger.warn(value.variable().getName() + " = " + value.getName() + " -- not good value assigned (noGood:" + noGood(assignment, value) + ")"); 177 setGood(assignment, value); 178 } 179 180 HashSet<T> noGood = new HashSet<T>(1); 181 noGood.add(value); 182 for (Iterator<T> i = value.variable().values(assignment).iterator(); i.hasNext();) { 183 T anotherValue = i.next(); 184 if (anotherValue.equals(value)) 185 continue; 186 setNoGood(assignment, anotherValue, noGood); 187 } 188 getContext(assignment).propagate(assignment, value.variable()); 189 } 190 191 /** 192 * After a value is unassigned: explanations of all values of unassigned 193 * variable are recomputed ({@link Value#conflicts(Assignment)}), propagation undo 194 * over the unassigned variable takes place. 195 */ 196 @Override 197 public void afterUnassigned(Assignment<V, T> assignment, long iteration, T value) { 198 iIteration = iteration; 199 if (!isGood(assignment, value)) 200 sLogger.error(value.variable().getName() + " = " + value.getName() 201 + " -- not good value unassigned (noGood:" + noGood(assignment, value) + ")"); 202 for (Iterator<T> i = value.variable().values(assignment).iterator(); i.hasNext();) { 203 T anotherValue = i.next(); 204 if (!isGood(assignment, anotherValue)) { 205 Set<T> noGood = anotherValue.conflicts(assignment); 206 if (noGood == null) 207 setGood(assignment, anotherValue); 208 else 209 setNoGood(assignment, anotherValue, noGood); 210 } 211 } 212 getContext(assignment).undoPropagate(assignment, value.variable()); 213 } 214 215 /** good values of a variable (values not removed from variables domain) 216 * @param assignment current assignment 217 * @param variable given variable 218 * @return set of good values 219 **/ 220 public Set<T> goodValues(Assignment<V, T> assignment, V variable) { 221 return getContext(assignment).goodValues(variable); 222 } 223 224 /** notification that a nogood value becomes good or vice versa */ 225 private void goodnessChanged(Assignment<V, T> assignment, T value) { 226 if (isGood(assignment, value)) { 227 goodValues(assignment, value.variable()).add(value); 228 } else { 229 goodValues(assignment, value.variable()).remove(value); 230 } 231 } 232 233 /** removes support of a variable */ 234 private void removeSupport(Assignment<V, T> assignment, V variable, T value) { 235 getContext(assignment).supportValues(variable).remove(value); 236 } 237 238 /** adds support of a variable */ 239 private void addSupport(Assignment<V, T> assignment, V variable, T value) { 240 getContext(assignment).supportValues(variable).add(value); 241 } 242 243 /** variables explanation 244 * @param assignment current assignment 245 * @param value given value 246 * @return no-good for the value 247 **/ 248 public Set<T> noGood(Assignment<V, T> assignment, T value) { 249 return getContext(assignment).getNoGood(value); 250 } 251 252 /** is variable good 253 * @param assignment current assignment 254 * @param value given value 255 * @return true if there is no no-good set for the value 256 **/ 257 public boolean isGood(Assignment<V, T> assignment, T value) { 258 return getContext(assignment).getNoGood(value) == null; 259 } 260 261 /** sets value to be good 262 * @param assignment current assignment 263 * @param value given value 264 **/ 265 protected void setGood(Assignment<V, T> assignment, T value) { 266 Set<T> noGood = getContext(assignment).getNoGood(value); 267 if (noGood != null) 268 for (T v : noGood) 269 removeSupport(assignment, v.variable(), value); 270 getContext(assignment).setNoGood(value, null); 271 goodnessChanged(assignment, value); 272 } 273 274 /** sets value's explanation 275 * @param assignment current assignment 276 * @param value given value 277 * @param reason no-good set for the value 278 **/ 279 public void setNoGood(Assignment<V, T> assignment, T value, Set<T> reason) { 280 Set<T> noGood = noGood(assignment, value); 281 if (noGood != null) 282 for (T v : noGood) 283 removeSupport(assignment, v.variable(), value); 284 getContext(assignment).setNoGood(value, reason); 285 for (T aValue : reason) 286 addSupport(assignment, aValue.variable(), value); 287 goodnessChanged(assignment, value); 288 } 289 290 private Set<T> reason(Assignment<V, T> assignment, Constraint<V, T> constraint, V aVariable, T aValue) { 291 Set<T> ret = new HashSet<T>(); 292 293 for (Iterator<T> i = aVariable.values(assignment).iterator(); i.hasNext();) { 294 T value = i.next(); 295 if (constraint.isConsistent(aValue, value)) { 296 if (noGood(assignment, value) == null) 297 sLogger.error("Something went wrong: value " + value + " cannot participate in a reason."); 298 else 299 ret.addAll(noGood(assignment, value)); 300 } 301 } 302 return ret; 303 } 304 305 @Override 306 public NoGood createAssignmentContext(Assignment<V, T> assignment) { 307 return new NoGood(assignment); 308 } 309 310 /** Propagation over the given variable. 311 * @param assignment current assignment 312 * @param variable given variable 313 **/ 314 protected void propagate(Assignment<V, T> assignment, V variable) { 315 getContext(assignment).propagate(assignment, variable); 316 } 317 318 /** 319 * Propagation undo over the given variable. All values having given 320 * variable in their explanations needs to be recomputed. This is done in 321 * two phases: 1) values that contain this variable in explanation are 322 * returned back to domains (marked as good) 2) propagation over variables 323 * which contains a value that was marked as good takes place 324 * @param assignment current assignment 325 * @param variable given variable 326 */ 327 public void undoPropagate(Assignment<V, T> assignment, V variable) { 328 getContext(assignment).undoPropagate(assignment, variable); 329 } 330 331 /** 332 * Assignment context 333 */ 334 public class NoGood implements AssignmentContext { 335 private Map<V, Set<T>[]> iNoGood = new HashMap<V, Set<T>[]>(); 336 private Map<V, Map<T, Set<T>>> iNoGoodVal = new HashMap<V, Map<T, Set<T>>>(); 337 338 /** 339 * Initialization. Enforce arc-consistency over the current (initial) 340 * solution. AC3 algorithm is used. 341 */ 342 public NoGood(Assignment<V, T> assignment) { 343 iProgress = Progress.getInstance(getModel()); 344 iProgress.save(); 345 iProgress.setPhase("Initializing propagation:", 3 * getModel().variables().size()); 346 for (Iterator<V> i = getModel().variables().iterator(); i.hasNext();) { 347 V aVariable = i.next(); 348 supportValues(aVariable).clear(); 349 goodValues(aVariable).clear(); 350 } 351 for (Iterator<V> i = getModel().variables().iterator(); i.hasNext();) { 352 V aVariable = i.next(); 353 for (Iterator<T> j = aVariable.values(assignment).iterator(); j.hasNext();) { 354 T aValue = j.next(); 355 Set<T> noGood = aValue.conflicts(assignment); 356 setNoGood(aValue, noGood); 357 if (noGood == null) { 358 goodValues(aVariable).add(aValue); 359 } else { 360 } 361 } 362 iProgress.incProgress(); 363 } 364 Queue<V> queue = new LinkedList<V>(); 365 for (Iterator<V> i = getModel().variables().iterator(); i.hasNext();) { 366 V aVariable = i.next(); 367 for (Constraint<V, T> constraint : aVariable.hardConstraints()) { 368 propagate(assignment, constraint, aVariable, queue); 369 } 370 iProgress.incProgress(); 371 } 372 if (!iJustForwardCheck) 373 propagate(assignment, queue); 374 for (Iterator<V> i = getModel().variables().iterator(); i.hasNext();) { 375 V aVariable = i.next(); 376 List<T> values2delete = new ArrayList<T>(); 377 for (Iterator<T> j = aVariable.values(assignment).iterator(); j.hasNext();) { 378 T aValue = j.next(); 379 if (getNoGood(aValue) != null && getNoGood(aValue).isEmpty()) { 380 values2delete.add(aValue); 381 } 382 } 383 for (T val : values2delete) 384 aVariable.removeValue(0, val); 385 if (aVariable.values(assignment).isEmpty()) { 386 sLogger.error(aVariable.getName() + " has empty domain!"); 387 } 388 iProgress.incProgress(); 389 } 390 iProgress.restore(); 391 } 392 393 public Set<T>[] getNoGood(V variable) { 394 return iNoGood.get(variable); 395 } 396 397 public void setNoGood(V variable, Set<T>[] noGood) { 398 if (noGood == null) 399 iNoGood.remove(variable); 400 else 401 iNoGood.put(variable, noGood); 402 } 403 404 public Set<T> getNoGood(T value) { 405 Map<T, Set<T>> ng = iNoGoodVal.get(value.variable()); 406 if (ng == null) return null; 407 return ng.get(value); 408 } 409 410 public void setNoGood(T value, Set<T> noGood) { 411 Map<T, Set<T>> ng = iNoGoodVal.get(value.variable()); 412 if (ng == null) { 413 ng = new HashMap<T, Set<T>>(); 414 iNoGoodVal.put(value.variable(), ng); 415 } 416 if (noGood == null) 417 ng.remove(value); 418 else 419 ng.put(value, noGood); 420 } 421 422 /** support values of a variable */ 423 @SuppressWarnings("unchecked") 424 private Set<T> supportValues(V variable) { 425 Set<T>[] ret = getNoGood(variable); 426 if (ret == null) { 427 ret = new Set[] { new HashSet<T>(1000), new HashSet<T>() }; 428 setNoGood(variable, ret); 429 } 430 return ret[0]; 431 } 432 433 /** good values of a variable (values not removed from variables domain) 434 * @param variable given variable 435 * @return set of good values 436 **/ 437 @SuppressWarnings("unchecked") 438 private Set<T> goodValues(V variable) { 439 Set<T>[] ret = getNoGood(variable); 440 if (ret == null) { 441 ret = new Set[] { new HashSet<T>(1000), new HashSet<T>() }; 442 setNoGood(variable, ret); 443 } 444 return ret[1]; 445 } 446 447 /** propagation over a constraint */ 448 private void propagate(Assignment<V, T> assignment, Constraint<V, T> constraint, V aVariable, Queue<V> queue) { 449 if (goodValues(aVariable).isEmpty()) 450 return; 451 List<T> conflicts = conflictValues(assignment, constraint, aVariable); 452 453 if (conflicts != null && !conflicts.isEmpty()) { 454 for (T conflictValue : conflicts) { 455 if (!queue.contains(conflictValue.variable())) 456 queue.add(conflictValue.variable()); 457 Set<T> reason = reason(assignment, constraint, aVariable, conflictValue); 458 // sLogger.debug(" "+conflictValue+" become nogood (c:"+constraint.getName()+", r:"+reason+")"); 459 setNoGood(conflictValue, reason); 460 if (reason.isEmpty()) 461 (conflictValue.variable()).removeValue(iIteration, conflictValue); 462 } 463 } 464 } 465 466 protected boolean propagate(Assignment<V, T> assignment, V aVariable, V anotherVariable, List<T> adepts) { 467 if (goodValues(aVariable).isEmpty()) 468 return false; 469 boolean ret = false; 470 List<T> conflicts = null; 471 for (Constraint<V, T> constraint : anotherVariable.constraintVariables().get(aVariable)) { 472 for (T aValue : goodValues(aVariable)) { 473 if (conflicts == null) 474 conflicts = conflictValues(constraint, aValue, adepts); 475 else 476 conflicts = conflictValues(constraint, aValue, conflicts); 477 if (conflicts == null || conflicts.isEmpty()) 478 break; 479 } 480 if (conflicts != null && !conflicts.isEmpty()) 481 for (T conflictValue : conflicts) { 482 Set<T> reason = reason(assignment, constraint, aVariable, conflictValue); 483 // sLogger.debug(" "+conflictValue+" become nogood (c:"+constraint.getName()+", r:"+reason+")"); 484 setNoGood(conflictValue, reason); 485 adepts.remove(conflictValue); 486 if (reason.isEmpty()) 487 (conflictValue.variable()).removeValue(iIteration, conflictValue); 488 ret = true; 489 } 490 } 491 return ret; 492 } 493 494 protected boolean propagate(Assignment<V, T> assignment, V aVariable, V anotherVariable) { 495 if (goodValues(anotherVariable).isEmpty()) 496 return false; 497 return propagate(assignment, aVariable, anotherVariable, new ArrayList<T>(goodValues(anotherVariable))); 498 } 499 500 /** Propagation over the given variable. 501 * @param assignment current assignment 502 * @param variable given variable 503 **/ 504 protected void propagate(Assignment<V, T> assignment, V variable) { 505 Queue<V> queue = new LinkedList<V>(); 506 if (assignment.getValue(variable) != null) { 507 for (Constraint<V, T> constraint : variable.hardConstraints()) { 508 if (contains(constraint)) 509 propagate(assignment, constraint, assignment.getValue(variable), queue); 510 } 511 } else { 512 for (Constraint<V, T> constraint : variable.hardConstraints()) { 513 if (contains(constraint)) 514 propagate(assignment, constraint, variable, queue); 515 } 516 } 517 if (!iJustForwardCheck && !queue.isEmpty()) 518 propagate(assignment, queue); 519 } 520 521 /** Propagation over the queue of variables. 522 * @param assignment current assignment 523 * @param queue variable queue 524 **/ 525 protected void propagate(Assignment<V, T> assignment, Queue<V> queue) { 526 while (!queue.isEmpty()) { 527 V aVariable = queue.poll(); 528 for (Constraint<V, T> constraint : aVariable.hardConstraints()) { 529 if (contains(constraint)) 530 propagate(assignment, constraint, aVariable, queue); 531 } 532 } 533 } 534 535 /** propagation over a constraint */ 536 private void propagate(Assignment<V, T> assignment, Constraint<V, T> constraint, T anAssignedValue, Queue<V> queue) { 537 Set<T> reason = new HashSet<T>(1); 538 reason.add(anAssignedValue); 539 Collection<T> conflicts = conflictValues(assignment, constraint, anAssignedValue); 540 if (conflicts != null && !conflicts.isEmpty()) 541 for (T conflictValue : conflicts) { 542 // sLogger.debug(" "+conflictValue+" become nogood (c:"+constraint.getName()+", r:"+reason+")"); 543 setNoGood(conflictValue, reason); 544 if (!queue.contains(conflictValue.variable())) 545 queue.add(conflictValue.variable()); 546 } 547 } 548 549 /** 550 * Propagation undo over the given variable. All values having given 551 * variable in thair explanations needs to be recomputed. This is done in 552 * two phases: 1) values that contain this variable in explanation are 553 * returned back to domains (marked as good) 2) propagation over variables 554 * which contains a value that was marked as good takes place 555 * @param assignment current assignment 556 * @param variable given variable 557 */ 558 public void undoPropagate(Assignment<V, T> assignment, V variable) { 559 Map<V, List<T>> undoVars = new HashMap<V, List<T>>(); 560 NoGood context = getContext(assignment); 561 while (!context.supportValues(variable).isEmpty()) { 562 T value = context.supportValues(variable).iterator().next(); 563 Set<T> noGood = value.conflicts(assignment); 564 if (noGood == null) { 565 setGood(assignment, value); 566 List<T> values = undoVars.get(value.variable()); 567 if (values == null) { 568 values = new ArrayList<T>(); 569 undoVars.put(value.variable(), values); 570 } 571 values.add(value); 572 } else { 573 setNoGood(value, noGood); 574 if (noGood.isEmpty()) 575 (value.variable()).removeValue(iIteration, value); 576 } 577 } 578 579 Queue<V> queue = new LinkedList<V>(); 580 for (V aVariable : undoVars.keySet()) { 581 List<T> values = undoVars.get(aVariable); 582 boolean add = false; 583 for (V x : aVariable.constraintVariables().keySet()) { 584 if (propagate(assignment, x, aVariable, values)) 585 add = true; 586 } 587 if (add) 588 queue.add(aVariable); 589 } 590 for (V x : variable.constraintVariables().keySet()) { 591 if (propagate(assignment, x, variable) && !queue.contains(variable)) 592 queue.add(variable); 593 } 594 if (!iJustForwardCheck) 595 propagate(assignment, queue); 596 } 597 598 private List<T> conflictValues(Assignment<V, T> assignment, Constraint<V, T> constraint, T aValue) { 599 List<T> ret = new ArrayList<T>(); 600 601 for (V variable : constraint.variables()) { 602 if (variable.equals(aValue.variable())) 603 continue; 604 if (assignment.getValue(variable) != null) 605 continue; 606 607 for (T value : goodValues(variable)) 608 if (!constraint.isConsistent(aValue, value)) 609 ret.add(value); 610 } 611 return ret; 612 } 613 614 private List<T> conflictValues(Constraint<V, T> constraint, T aValue, List<T> values) { 615 List<T> ret = new ArrayList<T>(values.size()); 616 for (T value : values) 617 if (!constraint.isConsistent(aValue, value)) 618 ret.add(value); 619 return ret; 620 } 621 622 private List<T> conflictValues(Assignment<V, T> assignment, Constraint<V, T> constraint, V aVariable) { 623 List<T> conflicts = null; 624 for (T aValue : goodValues(aVariable)) { 625 if (conflicts == null) 626 conflicts = conflictValues(assignment, constraint, aValue); 627 else 628 conflicts = conflictValues(constraint, aValue, conflicts); 629 if (conflicts == null || conflicts.isEmpty()) 630 return null; 631 } 632 return conflicts; 633 } 634 } 635 636}