You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DataValidationEvaluator.java 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.ss.formula;
  16. import java.util.ArrayList;
  17. import java.util.Collections;
  18. import java.util.HashMap;
  19. import java.util.List;
  20. import java.util.Map;
  21. import org.apache.poi.ss.formula.eval.BlankEval;
  22. import org.apache.poi.ss.formula.eval.BoolEval;
  23. import org.apache.poi.ss.formula.eval.ErrorEval;
  24. import org.apache.poi.ss.formula.eval.NumberEval;
  25. import org.apache.poi.ss.formula.eval.RefEval;
  26. import org.apache.poi.ss.formula.eval.StringEval;
  27. import org.apache.poi.ss.formula.eval.ValueEval;
  28. import org.apache.poi.ss.usermodel.Cell;
  29. import org.apache.poi.ss.usermodel.CellType;
  30. import org.apache.poi.ss.usermodel.DataValidation;
  31. import org.apache.poi.ss.usermodel.DataValidationConstraint;
  32. import org.apache.poi.ss.usermodel.DataValidationConstraint.OperatorType;
  33. import org.apache.poi.ss.usermodel.DataValidationConstraint.ValidationType;
  34. import org.apache.poi.ss.usermodel.Sheet;
  35. import org.apache.poi.ss.usermodel.Workbook;
  36. import org.apache.poi.ss.util.CellRangeAddressBase;
  37. import org.apache.poi.ss.util.CellRangeAddressList;
  38. import org.apache.poi.ss.util.CellReference;
  39. import org.apache.poi.ss.util.SheetUtil;
  40. /**
  41. * Evaluates Data Validation constraints.<p>
  42. *
  43. * For performance reasons, this class keeps a cache of all previously retrieved {@link DataValidation} instances.
  44. * Be sure to call {@link #clearAllCachedValues()} if any workbook validation definitions are
  45. * added, modified, or deleted.
  46. * <p>
  47. * Changing cell values should be fine, as long as the corresponding {@link WorkbookEvaluator#clearAllCachedResultValues()}
  48. * is called as well.
  49. *
  50. */
  51. public class DataValidationEvaluator {
  52. /**
  53. * Expensive to compute, so cache them as they are retrieved.
  54. * <p>
  55. * Sheets don't implement equals, and since its an interface,
  56. * there's no guarantee instances won't be recreated on the fly by some implementation.
  57. * So we use sheet name.
  58. */
  59. private final Map<String, List<? extends DataValidation>> validations = new HashMap<>();
  60. private final Workbook workbook;
  61. private final WorkbookEvaluator workbookEvaluator;
  62. /**
  63. * Use the same formula evaluation context used for other operations, so cell value
  64. * changes are automatically noticed
  65. * @param wb the workbook this operates on
  66. * @param provider provider for formula evaluation
  67. */
  68. public DataValidationEvaluator(Workbook wb, WorkbookEvaluatorProvider provider) {
  69. this.workbook = wb;
  70. this.workbookEvaluator = provider._getWorkbookEvaluator();
  71. }
  72. /**
  73. * @return evaluator
  74. */
  75. protected WorkbookEvaluator getWorkbookEvaluator() {
  76. return workbookEvaluator;
  77. }
  78. /**
  79. * Call this whenever validation structures change,
  80. * so future results stay in sync with the Workbook state.
  81. */
  82. public void clearAllCachedValues() {
  83. validations.clear();
  84. }
  85. /**
  86. * Lazy load validations by sheet, since reading the CT* types is expensive
  87. *
  88. * @param sheet The {@link Sheet} to load validations for.
  89. * @return The {@link DataValidation}s for the sheet
  90. */
  91. private List<? extends DataValidation> getValidations(Sheet sheet) {
  92. List<? extends DataValidation> dvs = validations.get(sheet.getSheetName());
  93. if (dvs == null && !validations.containsKey(sheet.getSheetName())) {
  94. dvs = sheet.getDataValidations();
  95. validations.put(sheet.getSheetName(), dvs);
  96. }
  97. return dvs;
  98. }
  99. /**
  100. * Finds and returns the {@link DataValidation} for the cell, if there is
  101. * one. Lookup is based on the first match from
  102. * {@link DataValidation#getRegions()} for the cell's sheet. DataValidation
  103. * regions must be in the same sheet as the DataValidation. Allowed values
  104. * expressions may reference other sheets, however.
  105. *
  106. * @param cell reference to check - use this in case the cell does not actually exist yet
  107. * @return the DataValidation applicable to the given cell, or null if no
  108. * validation applies
  109. */
  110. public DataValidation getValidationForCell(CellReference cell) {
  111. final DataValidationContext vc = getValidationContextForCell(cell);
  112. return vc == null ? null : vc.getValidation();
  113. }
  114. /**
  115. * Finds and returns the {@link DataValidationContext} for the cell, if there is
  116. * one. Lookup is based on the first match from
  117. * {@link DataValidation#getRegions()} for the cell's sheet. DataValidation
  118. * regions must be in the same sheet as the DataValidation. Allowed values
  119. * expressions may reference other sheets, however.
  120. *
  121. * @param cell reference to check
  122. * @return the DataValidationContext applicable to the given cell, or null if no
  123. * validation applies
  124. */
  125. public DataValidationContext getValidationContextForCell(CellReference cell) {
  126. final Sheet sheet = workbook.getSheet(cell.getSheetName());
  127. if (sheet == null) return null;
  128. final List<? extends DataValidation> dataValidations = getValidations(sheet);
  129. if (dataValidations == null) return null;
  130. for (DataValidation dv : dataValidations) {
  131. final CellRangeAddressList regions = dv.getRegions();
  132. if (regions == null) return null;
  133. // current implementation can't return null
  134. for (CellRangeAddressBase range : regions.getCellRangeAddresses()) {
  135. if (range.isInRange(cell)) {
  136. return new DataValidationContext(dv, this, range, cell);
  137. }
  138. }
  139. }
  140. return null;
  141. }
  142. /**
  143. * If {@link #getValidationForCell(CellReference)} returns an instance, and the
  144. * {@link ValidationType} is {@link ValidationType#LIST}, return the valid
  145. * values, whether they are from a static list or cell range.
  146. * <p>
  147. * For all other validation types, or no validation at all, this method
  148. * returns null.
  149. * <p>
  150. * This method could throw an exception if the validation type is not LIST,
  151. * but since this method is mostly useful in UI contexts, null seems the
  152. * easier path.
  153. *
  154. * @param cell reference to check - use this in case the cell does not actually exist yet
  155. * @return returns an unmodifiable {@link List} of {@link ValueEval}s if applicable, or
  156. * null
  157. */
  158. public List<ValueEval> getValidationValuesForCell(CellReference cell) {
  159. DataValidationContext context = getValidationContextForCell(cell);
  160. if (context == null) return null;
  161. return getValidationValuesForConstraint(context);
  162. }
  163. /**
  164. * static so enums can reference it without creating a whole instance
  165. * @return returns an unmodifiable {@link List} of {@link ValueEval}s, which may be empty
  166. */
  167. protected static List<ValueEval> getValidationValuesForConstraint(DataValidationContext context) {
  168. final DataValidationConstraint val = context.getValidation().getValidationConstraint();
  169. if (val.getValidationType() != ValidationType.LIST) return null;
  170. String formula = val.getFormula1();
  171. final List<ValueEval> values = new ArrayList<>();
  172. if (val.getExplicitListValues() != null && val.getExplicitListValues().length > 0) {
  173. // assumes parsing interprets the overloaded property right for XSSF
  174. for (String s : val.getExplicitListValues()) {
  175. if (s != null) values.add(new StringEval(s)); // constructor throws exception on null
  176. }
  177. } else if (formula != null) {
  178. // evaluate formula for cell refs then get their values
  179. // note this should return the raw formula result, not the "unwrapped" version that returns a single value.
  180. ValueEval eval = context.getEvaluator().getWorkbookEvaluator().evaluateList(formula, context.getTarget(), context.getRegion());
  181. // formula is a StringEval if the validation is by a fixed list. Use the explicit list later.
  182. // there is no way from the model to tell if the list is fixed values or formula based.
  183. if (eval instanceof TwoDEval) {
  184. TwoDEval twod = (TwoDEval) eval;
  185. for (int i=0; i < twod.getHeight(); i++) {
  186. final ValueEval cellValue = twod.getValue(i, 0);
  187. values.add(cellValue);
  188. }
  189. }
  190. }
  191. return Collections.unmodifiableList(values);
  192. }
  193. /**
  194. * Use the validation returned by {@link #getValidationForCell(CellReference)} if you
  195. * want the error display details. This is the validation checked by this
  196. * method, which attempts to replicate Excel's data validation rules.
  197. * <p>
  198. * Note that to properly apply some validations, care must be taken to
  199. * offset the base validation formula by the relative position of the
  200. * current cell, or the wrong value is checked.
  201. *
  202. * @param cellRef The reference of the cell to evaluate
  203. * @return true if the cell has no validation or the cell value passes the
  204. * defined validation, false if it fails
  205. */
  206. public boolean isValidCell(CellReference cellRef) {
  207. final DataValidationContext context = getValidationContextForCell(cellRef);
  208. if (context == null) return true;
  209. final Cell cell = SheetUtil.getCell(workbook.getSheet(cellRef.getSheetName()), cellRef.getRow(), cellRef.getCol());
  210. // now we can validate the cell
  211. // if empty, return not allowed flag
  212. if ( cell == null
  213. || isType(cell, CellType.BLANK)
  214. || (isType(cell,CellType.STRING)
  215. && (cell.getStringCellValue() == null || cell.getStringCellValue().isEmpty())
  216. )
  217. ) {
  218. return context.getValidation().getEmptyCellAllowed();
  219. }
  220. // cell has a value
  221. return ValidationEnum.isValid(cell, context);
  222. }
  223. /**
  224. * Note that this assumes the cell cached value is up to date and in sync with data edits
  225. *
  226. * @param cell The {@link Cell} to check.
  227. * @param type The {@link CellType} to check for.
  228. * @return true if the cell or cached cell formula result type match the given type
  229. */
  230. public static boolean isType(Cell cell, CellType type) {
  231. final CellType cellType = cell.getCellType();
  232. return cellType == type
  233. || (cellType == CellType.FORMULA
  234. && cell.getCachedFormulaResultType() == type
  235. );
  236. }
  237. /**
  238. * Not calling it ValidationType to avoid confusion for now with DataValidationConstraint.ValidationType.
  239. * Definition order matches OOXML type ID indexes
  240. */
  241. public static enum ValidationEnum {
  242. ANY {
  243. public boolean isValidValue(Cell cell, DataValidationContext context) {
  244. return true;
  245. }
  246. },
  247. INTEGER {
  248. public boolean isValidValue(Cell cell, DataValidationContext context) {
  249. if (super.isValidValue(cell, context)) {
  250. // we know it is a number in the proper range, now check if it is an int
  251. final double value = cell.getNumericCellValue(); // can't get here without a valid numeric value
  252. return Double.compare(value, (int) value) == 0;
  253. }
  254. return false;
  255. }
  256. },
  257. DECIMAL,
  258. LIST {
  259. public boolean isValidValue(Cell cell, DataValidationContext context) {
  260. final List<ValueEval> valueList = getValidationValuesForConstraint(context);
  261. if (valueList == null) return true; // special case
  262. // compare cell value to each item
  263. for (ValueEval listVal : valueList) {
  264. ValueEval comp = listVal instanceof RefEval ? ((RefEval) listVal).getInnerValueEval(context.getSheetIndex()) : listVal;
  265. // any value is valid if the list contains a blank value per Excel help
  266. if (comp instanceof BlankEval) return true;
  267. if (comp instanceof ErrorEval) continue; // nothing to check
  268. if (comp instanceof BoolEval) {
  269. if (isType(cell, CellType.BOOLEAN) && ((BoolEval) comp).getBooleanValue() == cell.getBooleanCellValue() ) {
  270. return true;
  271. } else {
  272. continue; // check the rest
  273. }
  274. }
  275. if (comp instanceof NumberEval) {
  276. // could this have trouble with double precision/rounding errors and date/time values?
  277. // do we need to allow a "close enough" double fractional range?
  278. // I see 17 digits after the decimal separator in XSSF files, and for time values,
  279. // there are sometimes discrepancies in the final decimal place.
  280. // I don't have a validation test case yet though. - GW
  281. if (isType(cell, CellType.NUMERIC) && ((NumberEval) comp).getNumberValue() == cell.getNumericCellValue()) {
  282. return true;
  283. } else {
  284. continue; // check the rest
  285. }
  286. }
  287. if (comp instanceof StringEval) {
  288. // interestingly, in testing, a validation value of the string "TRUE" or "true"
  289. // did not match a boolean cell value of TRUE - so apparently cell type matters
  290. // also, Excel validation is case insensitive - "true" is valid for the list value "TRUE"
  291. if (isType(cell, CellType.STRING) && ((StringEval) comp).getStringValue().equalsIgnoreCase(cell.getStringCellValue())) {
  292. return true;
  293. } else {
  294. continue; // check the rest;
  295. }
  296. }
  297. }
  298. return false; // no matches
  299. }
  300. },
  301. DATE,
  302. TIME,
  303. TEXT_LENGTH {
  304. public boolean isValidValue(Cell cell, DataValidationContext context) {
  305. if (! isType(cell, CellType.STRING)) return false;
  306. String v = cell.getStringCellValue();
  307. return isValidNumericValue(Double.valueOf(v.length()), context);
  308. }
  309. },
  310. FORMULA {
  311. /**
  312. * Note the formula result must either be a boolean result, or anything not in error.
  313. * If boolean, value must be true to pass, anything else valid is also passing, errors fail.
  314. * @see org.apache.poi.ss.formula.DataValidationEvaluator.ValidationEnum#isValidValue(Cell, DataValidationContext)
  315. */
  316. public boolean isValidValue(Cell cell, DataValidationContext context) {
  317. // unwrapped single value
  318. ValueEval comp = context.getEvaluator().getWorkbookEvaluator().evaluate(context.getFormula1(), context.getTarget(), context.getRegion());
  319. if (comp instanceof RefEval) {
  320. comp = ((RefEval) comp).getInnerValueEval(((RefEval) comp).getFirstSheetIndex());
  321. }
  322. if (comp instanceof BlankEval) return true;
  323. if (comp instanceof ErrorEval) return false;
  324. if (comp instanceof BoolEval) {
  325. return ((BoolEval) comp).getBooleanValue();
  326. }
  327. // empirically tested in Excel - 0=false, any other number = true/valid
  328. // see test file DataValidationEvaluations.xlsx
  329. if (comp instanceof NumberEval) {
  330. return ((NumberEval) comp).getNumberValue() != 0;
  331. }
  332. return false; // anything else is false, such as text
  333. }
  334. },
  335. ;
  336. public boolean isValidValue(Cell cell, DataValidationContext context) {
  337. return isValidNumericCell(cell, context);
  338. }
  339. /**
  340. * Uses the cell value, which may be the cached formula result value.
  341. * We won't re-evaluate cells here. This validation would be after the cell value was updated externally.
  342. * Excel allows invalid values through methods like copy/paste, and only validates them when the user
  343. * interactively edits the cell.
  344. * @return if the cell is a valid numeric cell for the validation or not
  345. */
  346. protected boolean isValidNumericCell(Cell cell, DataValidationContext context) {
  347. if ( ! isType(cell, CellType.NUMERIC)) return false;
  348. Double value = Double.valueOf(cell.getNumericCellValue());
  349. return isValidNumericValue(value, context);
  350. }
  351. /**
  352. * Is the number a valid option for the validation?
  353. */
  354. protected boolean isValidNumericValue(Double value, final DataValidationContext context) {
  355. try {
  356. Double t1 = evalOrConstant(context.getFormula1(), context);
  357. // per Excel, a blank value for a numeric validation constraint formula validates true
  358. if (t1 == null) return true;
  359. Double t2 = null;
  360. if (context.getOperator() == OperatorType.BETWEEN || context.getOperator() == OperatorType.NOT_BETWEEN) {
  361. t2 = evalOrConstant(context.getFormula2(), context);
  362. // per Excel, a blank value for a numeric validation constraint formula validates true
  363. if (t2 == null) return true;
  364. }
  365. return OperatorEnum.values()[context.getOperator()].isValid(value, t1, t2);
  366. } catch (NumberFormatException e) {
  367. // one or both formulas are in error, not evaluating to a number, so the validation is false per Excel's behavior.
  368. return false;
  369. }
  370. }
  371. /**
  372. * Evaluate a numeric formula value as either a constant or numeric expression.
  373. * Note that Excel treats validations with constraint formulas that evaluate to null as valid,
  374. * but evaluations in error or non-numeric are marked invalid.
  375. *
  376. * @param formula The text of the formula or a numeric value
  377. * @param context The {@link DataValidationContext} which is used for evaluation
  378. * @return numeric value or null if not defined or the formula evaluates to an empty/missing cell.
  379. * @throws NumberFormatException if the formula is non-numeric when it should be
  380. */
  381. private Double evalOrConstant(String formula, DataValidationContext context) throws NumberFormatException {
  382. if (formula == null || formula.trim().isEmpty()) return null; // shouldn't happen, but just in case
  383. try {
  384. return Double.valueOf(formula);
  385. } catch (NumberFormatException e) {
  386. // must be an expression, then. Overloading by Excel in the file formats.
  387. }
  388. // note the call to the "unwrapped" version, which returns a single value
  389. ValueEval eval = context.getEvaluator().getWorkbookEvaluator().evaluate(formula, context.getTarget(), context.getRegion());
  390. if (eval instanceof RefEval) {
  391. eval = ((RefEval) eval).getInnerValueEval(((RefEval) eval).getFirstSheetIndex());
  392. }
  393. if (eval instanceof BlankEval) return null;
  394. if (eval instanceof NumberEval) return Double.valueOf(((NumberEval) eval).getNumberValue());
  395. if (eval instanceof StringEval) {
  396. final String value = ((StringEval) eval).getStringValue();
  397. if (value == null || value.trim().isEmpty()) return null;
  398. // try to parse the cell value as a double and return it
  399. return Double.valueOf(value);
  400. }
  401. throw new NumberFormatException("Formula '" + formula + "' evaluates to something other than a number");
  402. }
  403. /**
  404. * Validates against the type defined in context, as an index of the enum values array.
  405. * @param cell Cell to check validity of
  406. * @param context The Data Validation to check against
  407. * @return true if validation passes
  408. * @throws ArrayIndexOutOfBoundsException if the constraint type is an invalid index
  409. */
  410. public static boolean isValid(Cell cell, DataValidationContext context) {
  411. return values()[context.getValidation().getValidationConstraint().getValidationType()].isValidValue(cell, context);
  412. }
  413. }
  414. /**
  415. * Not calling it OperatorType to avoid confusion for now with DataValidationConstraint.OperatorType.
  416. * Definition order matches OOXML type ID indexes
  417. */
  418. public static enum OperatorEnum {
  419. BETWEEN {
  420. public boolean isValid(Double cellValue, Double v1, Double v2) {
  421. return cellValue.compareTo(v1) >= 0 && cellValue.compareTo(v2) <= 0;
  422. }
  423. },
  424. NOT_BETWEEN {
  425. public boolean isValid(Double cellValue, Double v1, Double v2) {
  426. return cellValue.compareTo(v1) < 0 || cellValue.compareTo(v2) > 0;
  427. }
  428. },
  429. EQUAL {
  430. public boolean isValid(Double cellValue, Double v1, Double v2) {
  431. return cellValue.compareTo(v1) == 0;
  432. }
  433. },
  434. NOT_EQUAL {
  435. public boolean isValid(Double cellValue, Double v1, Double v2) {
  436. return cellValue.compareTo(v1) != 0;
  437. }
  438. },
  439. GREATER_THAN {
  440. public boolean isValid(Double cellValue, Double v1, Double v2) {
  441. return cellValue.compareTo(v1) > 0;
  442. }
  443. },
  444. LESS_THAN {
  445. public boolean isValid(Double cellValue, Double v1, Double v2) {
  446. return cellValue.compareTo(v1) < 0;
  447. }
  448. },
  449. GREATER_OR_EQUAL {
  450. public boolean isValid(Double cellValue, Double v1, Double v2) {
  451. return cellValue.compareTo(v1) >= 0;
  452. }
  453. },
  454. LESS_OR_EQUAL {
  455. public boolean isValid(Double cellValue, Double v1, Double v2) {
  456. return cellValue.compareTo(v1) <= 0;
  457. }
  458. },
  459. ;
  460. public static final OperatorEnum IGNORED = BETWEEN;
  461. /**
  462. * Evaluates comparison using operator instance rules
  463. * @param cellValue won't be null, assumption is previous checks handled that
  464. * @param v1 if null, value assumed invalid, anything passes, per Excel behavior
  465. * @param v2 null if not needed. If null when needed, assume anything passes, per Excel behavior
  466. * @return true if the comparison is valid
  467. */
  468. public abstract boolean isValid(Double cellValue, Double v1, Double v2);
  469. }
  470. /**
  471. * This class organizes and encapsulates all the pieces of information related to a single
  472. * data validation configuration for a single cell. It cleanly separates the validation region,
  473. * the cells it applies to, the specific cell this instance references, and the validation
  474. * configuration and current values if applicable.
  475. */
  476. public static class DataValidationContext {
  477. private final DataValidation dv;
  478. private final DataValidationEvaluator dve;
  479. private final CellRangeAddressBase region;
  480. private final CellReference target;
  481. /**
  482. * Populate the context with the necessary values.
  483. */
  484. public DataValidationContext(DataValidation dv, DataValidationEvaluator dve, CellRangeAddressBase region, CellReference target) {
  485. this.dv = dv;
  486. this.dve = dve;
  487. this.region = region;
  488. this.target = target;
  489. }
  490. /**
  491. * @return the dv
  492. */
  493. public DataValidation getValidation() {
  494. return dv;
  495. }
  496. /**
  497. * @return the dve
  498. */
  499. public DataValidationEvaluator getEvaluator() {
  500. return dve;
  501. }
  502. /**
  503. * @return the region
  504. */
  505. public CellRangeAddressBase getRegion() {
  506. return region;
  507. }
  508. /**
  509. * @return the target
  510. */
  511. public CellReference getTarget() {
  512. return target;
  513. }
  514. public int getOffsetColumns() {
  515. return target.getCol() - region.getFirstColumn();
  516. }
  517. public int getOffsetRows() {
  518. return target.getRow() - region.getFirstRow();
  519. }
  520. public int getSheetIndex() {
  521. return dve.getWorkbookEvaluator().getSheetIndex(target.getSheetName());
  522. }
  523. public String getFormula1() {
  524. return dv.getValidationConstraint().getFormula1();
  525. }
  526. public String getFormula2() {
  527. return dv.getValidationConstraint().getFormula2();
  528. }
  529. public int getOperator() {
  530. return dv.getValidationConstraint().getOperator();
  531. }
  532. }
  533. }