View Javadoc

1   /*
2    * Copyright (c) 2003-2008 by Cosylab d. d.
3    *
4    * This file is part of Java-Common.
5    *
6    * Java-Common is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU General Public License as published by
8    * the Free Software Foundation, either version 3 of the License, or
9    * (at your option) any later version.
10   *
11   * Java-Common is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU General Public License for more details.
15   *
16   * You should have received a copy of the GNU General Public License
17   * along with Java-Common.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  
20  package com.cosylab.util;
21  
22  import java.util.Arrays;
23  import java.util.Comparator;
24  
25  
26  /**
27   * <code>NumericStringComparator</code> ... NumericStringComparator represents
28   * class which compares two Strings. Roules are followed:
29   * 
30   * <ol>
31   * <li>
32   * when there is no number or the strings are different before the first
33   * number, result should be the same as str1.compareTo(str2)
34   * 
35   * <ul>
36   * <li>
37   * "klemen" &lt; "miha"
38   * </li>
39   * <li>
40   * "klemen12" &lt; "miha600"
41   * </li>
42   * </ul>
43   * 
44   * </li>
45   * <li>
46   * when two strings are the same up to the first number, the whole number is
47   * taken into account and ordered by the result
48   * 
49   * <ul>
50   * <li>
51   * "miha8" &lt; "miha12"
52   * </li>
53   * <li>
54   * "miha6_klemen3_janez" &lt; "miha6_klemen22a"
55   * </li>
56   * <li>
57   * "miha8y" &lt; "miha13a"
58   * </li>
59   * </ul>
60   * 
61   * </li>
62   * <li>
63   * Whitespaces,zeros, and "_" characters preceding numbers should be omitted
64   * from the comparison, unless the omission would cause equality:
65   * 
66   * <ul>
67   * <li>
68   * "klemen 12" &lt; "klemen32"
69   * </li>
70   * <li>
71   * "klemen001" &lt; "klemen12"
72   * </li>
73   * <li>
74   * "klemen__2" &lt; "klemen_13"
75   * </li>
76   * <li>
77   * "klemen__2" &lt; "klemen2"
78   * </li>
79   * <li>
80   * "klemen b" &lt; "klemena"  (should be followed by number)
81   * </li>
82   * <li>
83   * "miha0x" &lt; "mihax" (only zeros preceding a number should be omitted)
84   * </li>
85   * </ul>
86   * 
87   * </li>
88   * <li>
89   * All of the above should also apply to double numbers.
90   * 
91   * <ul>
92   * <li>
93   * "miha2.301bla" &lt; "miha12.1423aaa"
94   * </li>
95   * </ul>
96   * 
97   * </li>
98   * <li>
99   * When the compared parts of numbers are the same numerically (and written
100  * differently), they should be ordered alphabetically:
101  * 
102  * <ul>
103  * <li>
104  * "miha012klemen5abc" &lt; "miha12klemen5abc"
105  * </li>
106  * <li>
107  * "miha012klemen5abc" &lt; "miha12klemen05abc"
108  * </li>
109  * </ul>
110  * 
111  * </li>
112  * 
113  * <li>The <code>softEquals</code> property controls how the strictness of the comparator with regards
114  *     to spaces and separators. When <code>softEquals</code> is set to <code>true</code>, 
115  *     the comparator will return 0 even when two strings are not identical, but are equal
116  *     numerically. e.g. compare("100","0100") will return 0;
117  *     </li>
118  * </ol>
119  * 
120  *
121  * @author Andrej Kosmrlj (<a
122  *         href="mailto:andrej.kosmrlj@kgb.ijs.si">andrej.kosmrlj&x40;kgb.ijs.si</a>)
123  * @version $Id: NumericStringComparator.java,v 1.16 2008-04-22 12:26:29 jbobnar Exp $
124  *
125  * @see com.cosylab.util.test
126  * @since Oct 15, 2003.
127  */
128 
129 // TODO: Support java.text.Collator - based sorting for locale-dependent strings
130 public class NumericStringComparator implements Comparator
131 {
132 	/** Constant indicating that dot is treated as a decimal separator */
133 	public static final int DEC_SEPARATOR_DOT = 1;
134 
135 	/** Constant indicating that comma is treated as a decimal separator */
136 	public static final int DEC_SEPARATOR_COMMA = 2;
137 
138 	/**
139 	 * Constant indicating that both dot and comma are treated as decimal
140 	 * separators
141 	 */
142 	public static final int DEC_SEPARATOR_BOTH = 3;
143 
144 	/**
145 	 * Constant indicating that decimal separators should not be treated
146 	 * differently than other characters
147 	 */
148 	public static final int DEC_SEPARATOR_NONE = 4;
149 	private static final char COMMA = ',';
150 	private static final char DOT = '.';
151 	private static NumericStringComparator defaultInstance;
152 
153 	/**
154 	 * Convenience method for obtaining a default
155 	 * <code>NumericStringComparator</code>. Array of separators has default
156 	 * value: [' ', '_']. Decimal mode has default value:
157 	 * <code>DEC_SEPARATOR_DOT</code>.
158 	 *
159 	 * @return The default instance of comparator.
160 	 */
161 	public static NumericStringComparator getDefault()
162 	{
163 		if (defaultInstance == null) {
164 			defaultInstance = new NumericStringComparator();
165 		}
166 
167 		return defaultInstance;
168 	}
169 
170 	private boolean softEquals = false;
171 
172 	/** array of separators */
173 	private char[] separators;
174 
175 	/** decimal separator mode: NONE, DOT, COMMA, BOTH */
176 	private int decSeparator;
177 
178 	/**
179 	 * Contructor creates NumericStringComparator with specified array of
180 	 * separators in decimal separator mode. Separator is character which is
181 	 * not used in compare() method unless there are equal numbers in each
182 	 * String and if Strings are different only with separators
183 	 *
184 	 * @param separators array of separators
185 	 * @param decSeparator decimal separator mode
186 	 */
187 	public NumericStringComparator(char[] separators, int decSeparator)
188 	{
189 		setSeparators(separators);
190 		this.decSeparator = decSeparator;
191 	}
192 
193 	/**
194 	 * Contructor creates NumericStringComparator with specified array of
195 	 * separators. Decimal mode has default value: DEC_SEPARATOR_DOT.
196 	 *
197 	 * @param separators array of separators
198 	 */
199 	public NumericStringComparator(char[] separators)
200 	{
201 		this(separators, NumericStringComparator.DEC_SEPARATOR_DOT);
202 	}
203 
204 	/**
205 	 * Contructor creates NumericStringComparator with specified decimal mode.
206 	 * Array of separators has default value: [' ', '_'].
207 	 *
208 	 * @param decSeparator decimal mode
209 	 */
210 	public NumericStringComparator(int decSeparator)
211 	{
212 		this(new char[]{ ' ', '_' }, decSeparator);
213 	}
214 
215 	/**
216 	 * Contructor creates NumericStringComparator. Array of separators has
217 	 * default value: [' ', '_']. Decimal mode has default value:
218 	 * DEC_SEPARATOR_DOT.
219 	 */
220 	public NumericStringComparator()
221 	{
222 		this(NumericStringComparator.DEC_SEPARATOR_DOT);
223 	}
224 
225 	/**
226 	 * Method adds new separator.
227 	 *
228 	 * @param sep new separator
229 	 *
230 	 * @throws IllegalArgumentException when the separator is an illegal
231 	 *         character such as a digit, a decimal separator or  the minus
232 	 *         sign
233 	 */
234 	public void addSeparator(char sep)
235 	{
236 		if (Arrays.binarySearch(this.separators, sep) >= 0) {
237 			return;
238 		}
239 
240 		if (sep == '-') {
241 			throw new IllegalArgumentException(
242 			    "Minus sign should not be used as a separator.");
243 		}
244 
245 		if (isDecSeparator(sep)) {
246 			throw new IllegalArgumentException("Character '" + sep
247 			    + "' should not be used as a separator. It is defined as a decimal separator.");
248 		}
249 
250 		if (Character.isDigit(sep)) {
251 			throw new IllegalArgumentException(
252 			    "A digit should not be used as a separator.");
253 		}
254 
255 		char[] temp = new char[separators.length + 1];
256 		System.arraycopy(separators, 0, temp, 0, separators.length);
257 		temp[temp.length - 1] = sep;
258 		Arrays.sort(temp);
259 		this.separators = temp;
260 	}
261 
262 	/**
263 	 * Method sets separators
264 	 *
265 	 * @param sep array of separators
266 	 *
267 	 * @throws IllegalArgumentException when the separators array contains
268 	 *         illegal character such as digits, decimal separators or minus
269 	 *         sign
270 	 */
271 	public void setSeparators(char[] sep)
272 	{
273 		this.separators = new char[sep.length];
274 		System.arraycopy(sep, 0, separators, 0, sep.length);
275 		Arrays.sort(this.separators);
276 
277 		for (int i = 0; i < sep.length; i++) {
278 			if (isDecSeparator(sep[i])) {
279 				throw new IllegalArgumentException("Cannot set " + sep[i]
280 				    + " as separator. It is already set as decimal separator.");
281 			}
282 
283 			if (Character.isDigit(sep[i])) {
284 				throw new IllegalArgumentException("Cannot set " + sep[i]
285 				    + " as separator. It is a digit.");
286 			}
287 
288 			if (sep[i] == '-') {
289 				throw new IllegalArgumentException("Cannot set " + sep[i]
290 				    + " as separator. It is used as a negative sign.");
291 			}
292 		}
293 	}
294 
295 	/**
296 	 * Method returns separators
297 	 *
298 	 * @return char[] separators
299 	 */
300 	public char[] getSeparators()
301 	{
302 		return this.separators;
303 	}
304 
305 	/**
306 	 * Method returns decimal separator mode, represented by int
307 	 *
308 	 * @return int
309 	 */
310 	public int getDecSeparatorMode()
311 	{
312 		return this.decSeparator;
313 	}
314 
315 	/**
316 	 * Method returns true if character is digit or separator, otherwise
317 	 * returns false.
318 	 *
319 	 * @param ch character
320 	 *
321 	 * @return boolean
322 	 */
323 	private boolean isSeparatorOrDigit(char ch)
324 	{
325 		return (Character.isDigit(ch) || isDecSeparator(ch) || isSeparator(ch));
326 	}
327 
328 	/**
329 	 * Method returns true if character is separator, otherwise returns false.
330 	 *
331 	 * @param ch character
332 	 *
333 	 * @return boolean
334 	 */
335 	private final boolean isSeparator(char ch)
336 	{
337 		int idx = separators.length - 1;
338 
339 		while (idx >= 0) {
340 			if (separators[idx--] == ch) {
341 				return true;
342 			}
343 		}
344 
345 		return false;
346 	}
347 
348 	/**
349 	 * Method returns true if character is decimal separator, otherwise returns
350 	 * false.
351 	 *
352 	 * @param ch character
353 	 *
354 	 * @return boolean
355 	 */
356 	private final boolean isDecSeparator(final char ch)
357 	{
358 		switch (this.decSeparator) {
359 		case DEC_SEPARATOR_NONE:
360 			return false;
361 
362 		case DEC_SEPARATOR_BOTH:
363 			return (ch == DOT || ch == COMMA);
364 
365 		case DEC_SEPARATOR_COMMA:
366 			return ch == COMMA;
367 
368 		case DEC_SEPARATOR_DOT:
369 			return ch == DOT;
370 		}
371 
372 		return false;
373 	}
374 
375 	/* (non-Javadoc)
376 	 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
377 	 */
378 	public int compare(Object o1, Object o2)
379 	{
380 		String s1 = o1.toString();
381 		String s2 = o2.toString();
382         if (softEquals) {
383             s1=s1.trim();
384             s2=s2.trim();
385         }
386 		final int[] idx = { 0, 0 };
387 		final int[] len = { s1.length(), s2.length() };
388 
389 		if ((len[0] == 0) || (len[1] == 0)) {
390 			return s1.compareTo(s2);
391 		}
392 
393 		int[] ret = {0, 0, 1}; // {retComp, stringComp, sign}
394 
395 		while (true) {
396 			if (idx[0] >= len[0]) {
397 				if (softEquals) {
398 					return ret[0];
399 				}
400                 if (ret[0]!=0) {
401                     return ret[0];
402                 }
403 				return (ret[1] == 0) ? (idx[1] - len[1])*ret[2] : ret[1];
404 			} else if (idx[1] >= len[1]) {
405 				if (softEquals) {
406 					return ret[0];
407 				}
408 				return (ret[1] == 0) ? (len[0] - idx[0])*ret[2] : ret[1];
409 			}
410 
411 			if (processItem(s1, s2, idx, len, ret)) {
412 				return ret[0];
413 			}
414 		}
415 	}
416 
417 	/**
418 	 * Compares two items of the string at positions specified in idx[] and
419 	 * advances the indices.  Items are either characters or numeric sequences
420 	 * with separators.  If a distinct order was determined, <code>true</code>
421 	 * is returned, otherwise this method returns false.  The result is
422 	 * contained in the ret[], where the first number is the distinct
423 	 * comparison value, and the second is used in case two numeric sequences
424 	 * were equal numerically, but different by string comparison. This second
425 	 * element is not modified it it has previously been set to a non-zero
426 	 * value.
427 	 *
428 	 * @param s1
429 	 * @param s2
430 	 * @param idx
431 	 * @param len
432 	 * @param ret
433 	 *
434 	 * @return
435 	 */
436 	private final boolean processItem(String s1, String s2, int[] idx,
437 	    int[] len, int[] ret)
438 	{
439 		char c1;
440 		char c2;
441 		int nComp = 0; // holds comparison of the whole part
442 		boolean decFound = false; // did we find decimal separator
443 		boolean numFound = false; // did we find any number
444 		boolean sComp = softEquals?false:(ret[1] == 0); // is the string comparison still 0 (in case of numerical equality)
445 
446 		c1 = s1.charAt(idx[0]++);
447 		c2 = s2.charAt(idx[1]++);
448 
449 		// SKIP SEPARATORS OF 1
450 		while (isSeparator(c1) || c1 == '0') {
451 			if (sComp) { // We need this for String comparison if the numbers are equal
452 
453 				if (c1 != c2) {
454 					ret[1] = c1 - c2;
455 					sComp = false;
456 				} else {
457 					if (idx[1] == len[1]) {
458 						return false;
459 					}
460 
461 					c2 = s2.charAt(idx[1]++);
462 				}
463 			}
464 
465 			if (idx[0] == len[0]) {
466 				if (softEquals && c1 == '0') {
467 					break;
468 				} else {
469                     ret[0]=Character.isDigit(c2)?-1:ret[1];
470 					return false;
471 				}
472 			}
473 
474 			c1 = s1.charAt(idx[0]++);
475 		}
476 
477 		//SKIP (remaining) SEPARATORS OF 2
478 		while (isSeparator(c2) || c2 == '0') {
479 			if (sComp) { // We need this for String comparison if the numbers are equal
480 
481 				if (c1 != c2) {
482 					ret[1] = c1 - c2;
483 					sComp = false;
484 				} else {
485 					if (idx[0] == len[0]) {
486 						return false;
487 					}
488 
489 					c1 = s1.charAt(idx[0]++);
490 				}
491 			}
492 
493 			if (idx[1] == len[1]) {
494 				if (softEquals && c2 == '0') {
495 					break;
496 				} else {
497 					return false;
498 				}
499 			}
500 
501 			c2 = s2.charAt(idx[1]++);
502 		}
503 
504 		boolean minus1 = false;
505 
506 		if (c1 == '-') {
507 			minus1 = true;
508 
509 			if (idx[0] == len[0]) {
510 				return false;
511 			}
512 
513 			c1 = s1.charAt(idx[0]++);
514 		}
515 
516 		boolean minus2 = false;
517 
518 		if (c2 == '-') {
519 			minus2 = true;
520 
521 			if (idx[1] == len[1]) {
522 				return false;
523 			}
524 
525 			c2 = s2.charAt(idx[1]++);
526 		}
527 
528 		if (minus1 ^ minus2) {
529 			if ((isDecSeparator(c1) || Character.isDigit(c1))
530 			    && (isDecSeparator(c2) || Character.isDigit(c2))) {
531 				ret[0] = minus1 ? -1 : 1;
532 
533 				return true;
534 			} else if (sComp) { // Return string comparison
535 
536 				if (minus1) {
537 					ret[0] = '-' - c2;
538 				} else {
539 					ret[0] = c1 - '-';
540 				}
541 
542 				return true;
543 			}
544 		} else {
545             ret[2] = minus1?-1:1;
546         }
547 
548 		// PROCESS WHOLE PART
549 		while (Character.isDigit(c1)) {
550 			if (Character.isDigit(c2)) {
551 				numFound = true;
552 
553 				if (c1 != c2) {
554 					if (sComp) {
555 						ret[1] = c1 - c2; // "100" vs. "12"
556 						sComp = false;
557 					}
558 
559 					if (nComp == 0) {
560 						nComp = c1 - c2;
561 					}
562 				}
563 			} else {
564 				if (numFound) {
565 					ret[0] = 1; // "1000" vs. "100a"
566 				} else {
567 					ret[0] = c1 - c2; // "a12" vs. "ax";
568 				}
569 
570 				if (minus1 && numFound) {
571 					ret[0] = -ret[0];
572 				}
573 
574 				return ret[0] != 0;
575 			}
576 
577 			if (idx[0] >= len[0]) {
578 				if (idx[1] == len[1] || !Character.isDigit(s2.charAt(idx[1]))) {
579 					ret[0] = nComp;
580 				} else {
581 					ret[0] = idx[1] - len[1];
582 				}
583 
584 				if (minus1 && numFound) {
585 					ret[0] = -ret[0];
586 				}
587 
588 				return ret[0] != 0;
589 			} else if (idx[1] == len[1]) {
590 				if (!Character.isDigit(s1.charAt(idx[0]))) {
591 					ret[0] = nComp;
592 				} else {
593 					ret[0] = len[0] - idx[0];
594 				}
595 
596 				if (minus1 && numFound) {
597 					ret[0] = -ret[0];
598 				}
599 
600 				return ret[0] != 0;
601 			}
602 
603 			c1 = s1.charAt(idx[0]++);
604 			c2 = s2.charAt(idx[1]++);
605 		}
606 
607 		if (Character.isDigit(c2)) {
608             if (numFound) {
609                 ret[0] = -1; // "100a" vs. "1000"
610             } else {
611                 ret[0] = c1 - c2; // "ax" vs. "a12";
612             }
613 
614             if (minus2 && numFound) {
615                 ret[0] = -ret[0];
616             }
617 
618             return ret[0] != 0;
619 /*			ret[0] = minus1 ? 1 : -1; //          "100" vs. "1000"
620 
621 			return true;*/
622 		}
623 
624 		if (nComp != 0) {
625 			ret[0] = minus1 ? -nComp : nComp;
626 
627 			return true;
628 		}
629 
630 		// PROCESS DECIMAL SEPARATOR
631 		if (isDecSeparator(c1)) {
632 			if (isDecSeparator(c2)) {
633 				if (sComp && c1 != c2) { // "10,2" vs. "10.2"
634 					ret[1] = c1 - c2;
635 					sComp = false;
636 				}
637 
638 				if (idx[0] == len[0] || idx[1] == len[1]) {
639 					return false;
640 				}
641 
642 				c1 = s1.charAt(idx[0]++);
643 				c2 = s2.charAt(idx[1]++);
644 				decFound = true;
645 			} else { // "10.0000ab" vs. "10ab"
646 
647 				if (sComp) {
648 					ret[1] = c1 - c2; // '.' - 'a';
649 					sComp = false;
650 				}
651 
652 				if (idx[0] == len[0]) {
653 					return false;
654 				}
655 
656 				c1 = s1.charAt(idx[0]++);
657 
658 				while (c1 == '0') {
659 					if (idx[0] == len[0]) {
660 						return false;
661 					}
662 
663 					c1 = s1.charAt(idx[0]++);
664 				}
665 			}
666 		} else if (isDecSeparator(c2)) { // "10ab" vs "10.0ab"
667 
668 			if (sComp) {
669 				ret[1] = c1 - c2; // 'a' - '.';
670 				sComp = false;
671 			}
672 
673 			if (idx[1] == len[1]) {
674 				return false;
675 			}
676 
677 			c2 = s2.charAt(idx[1]++);
678 
679 			while (c2 == '0') {
680 				if (idx[1] == len[1]) {
681 					return false;
682 				}
683 
684 				c2 = s2.charAt(idx[1]++);
685 			}
686 			if (softEquals) return false;
687 			ret[0] = minus1 ? 1 : -1;
688 
689 			return true;
690 		}
691 
692 		//PROCESS FRACTIONAL PART 
693 		if (decFound) { // "123.1234" vs "123.1235"
694 
695 			while (true) {
696 				if (Character.isDigit(c1)) {
697 					if (Character.isDigit(c2)) {
698 						numFound = true;
699 
700 						if (c1 != c2) {
701 							ret[0] = c1 - c2;
702 
703 							if (minus1) {
704 								ret[0] = -ret[0];
705 							}
706 
707 							return true;
708 						}
709 					} else {
710 						if (sComp) {
711 							ret[1] = c1 - c2;
712 							sComp = false;
713 						}
714 
715 						while (c1 == '0') { //"10.2000" vs. "10.2"
716 
717 							if (idx[0] == len[0]) {
718 								return false;
719 							}
720 
721 							c1 = s1.charAt(idx[0]++);
722 						}
723 
724 						if (Character.isDigit(c1)) {
725 							ret[0] = 1; // "10.20001" vs. "10.2"
726 						}
727 
728 						if (minus1) {
729 							ret[0] = -ret[0];
730 						}
731 
732 						return ret[0] != 0;
733 					}
734 				} else if (Character.isDigit(c2)) {
735 					if (sComp) {
736 						ret[1] = c1 - c2;
737 						sComp = false;
738 					}
739 
740 					while (c2 == '0') { //"10.2" vs. "10.2000"
741 
742 						if (idx[1] == len[1]) {
743 							return false;
744 						}
745 
746 						c2 = s2.charAt(idx[1]++);
747 					}
748 
749 					if (Character.isDigit(c2)) {
750 						ret[0] = -1; // "10.2" vs. "10.20001"
751 					}
752 
753 					if (minus1) {
754 						ret[0] = -ret[0];
755 					}
756 
757 					return ret[0] != 0;
758 				} else if (!numFound || nComp == 0) {
759 					return false;
760 				}
761 
762 				if (idx[0] == len[0] || idx[1] == len[1]) {
763 					return false;
764 				}
765 
766 				c1 = s1.charAt(idx[0]++);
767 				c2 = s2.charAt(idx[1]++);
768 			}
769 		}
770 
771 		//PROCESS TRAILING SEPARATORS
772 		if (numFound) {
773 			// SKIP SEPARATORS OF 1
774 			while (isSeparator(c1)) {
775 				if (sComp) {
776 					if (c1 != c2) {
777 						ret[1] = c1 - c2;
778 						sComp = false;
779 					} else {
780 						if (idx[1] == len[1]) {
781 							return false;
782 						}
783 
784 						c2 = s2.charAt(idx[1]++);
785 					}
786 				}
787 
788 				if (idx[0] == len[0]) {
789 					return false;
790 				}
791 
792 				c1 = s1.charAt(idx[0]++);
793 			}
794 
795 			;
796 
797 			//SKIP (remaining) SEPARATORS OF 2
798 			while (isSeparator(c2)) {
799 				if (sComp) {
800 					if (c1 != c2) {
801 						ret[1] = c1 - c2;
802 						sComp = false;
803 					} else {
804 						if (idx[0] == len[0]) {
805 							return false;
806 						}
807 
808 						c1 = s1.charAt(idx[0]++);
809 					}
810 				}
811 
812 				c2 = s2.charAt(idx[1]++);
813 			}
814 		} else {
815 			if (ret[1] != 0) {
816 				ret[0] = ret[1];
817 
818 				if (minus1) {
819 					ret[0] = -ret[0];
820 				}
821 
822 				return true;
823 			}
824 		}
825 
826 		if (!numFound) {
827 			if (c1 != c2) {
828 				ret[0] = c1 - c2;
829 
830 				if (minus1) {
831 					ret[0] = -ret[0];
832 				}
833 
834 				return true;
835 			}
836 		} else {
837 			idx[0]--;
838 			idx[1]--;
839 		}
840 
841 		return false;
842 	}
843 
844 	/**
845 	 * When <code>softEquals</code> property is set to true, the comparator
846 	 * will return 0 even when two strings are not identical, but are equal
847 	 * numerically. e.g. compare("100","0100") will return 0;
848 	 *
849 	 * @return Returns the softEquals.
850 	 */
851 	public boolean isSoftEquals()
852 	{
853 		return softEquals;
854 	}
855 
856 	/**
857 	 * Sets the <code>softEquals</code> property.
858 	 *
859 	 * @param softEquals The softEquals to set.
860 	 *
861 	 * @see #isSoftEquals()
862 	 */
863 	public void setSoftEquals(boolean softEquals)
864 	{
865 		this.softEquals = softEquals;
866 	}
867 }
868 
869 /* __oOo__ */