001package jmri.util;
002
003/**
004 * Compare values.
005 *
006 * @author Daniel Bergqvist Copyright 2022
007 */
008public class CompareUtil {
009
010    public static enum CompareType {
011        NumberOrString(Bundle.getMessage("CompareUtil_CompareType_NumberOrString")),
012        String(Bundle.getMessage("CompareUtil_CompareType_String")),
013        Number(Bundle.getMessage("CompareUtil_CompareType_Number"));
014
015        private final String _text;
016
017        private CompareType(String text) {
018            this._text = text;
019        }
020
021        @Override
022        public String toString() {
023            return _text;
024        }
025
026    }
027
028    public static enum CompareOperation {
029        LessThan(Bundle.getMessage("CompareUtil_CompareOperation_LessThan")),
030        LessThanOrEqual(Bundle.getMessage("CompareUtil_CompareOperation_LessThanOrEqual")),
031        Equal(Bundle.getMessage("CompareUtil_CompareOperation_Equal")),
032        GreaterThanOrEqual(Bundle.getMessage("CompareUtil_CompareOperation_GreaterThanOrEqual")),
033        GreaterThan(Bundle.getMessage("CompareUtil_CompareOperation_GreaterThan")),
034        NotEqual(Bundle.getMessage("CompareUtil_CompareOperation_NotEqual"));
035
036        private final String _text;
037
038        private CompareOperation(String text) {
039            this._text = text;
040        }
041
042        @Override
043        public String toString() {
044            return _text;
045        }
046
047    }
048
049
050    /**
051     * Compare two values.
052     *
053     * @param type            the type
054     * @param oper            the operation
055     * @param value1          left side of the comparison
056     * @param value2          right side of the comparison
057     * @param caseInsensitive true if comparison should be case insensitive;
058     *                        false otherwise
059     * @return true if values compare per _memoryOperation; false otherwise
060     */
061    public static boolean compare(CompareType type, CompareOperation oper, Object value1, Object value2, boolean caseInsensitive) {
062        switch (type) // both are numbers
063        {
064            case NumberOrString:
065                return compareNumber(false, oper, value1, value2, caseInsensitive);
066            case String:
067                return compareString(oper, value1, value2, caseInsensitive);
068            case Number:
069                return compareNumber(true, oper, value1, value2, caseInsensitive);
070            default:
071                throw new IllegalArgumentException("type has unknown value: "+type.name());
072        }
073    }
074
075    /**
076     * Compare two values.
077     *
078     * @param oper            the operation
079     * @param value1          left side of the comparison
080     * @param value2          right side of the comparison
081     * @param caseInsensitive true if comparison should be case insensitive;
082     *                        false otherwise
083     * @return true if values compare per _memoryOperation; false otherwise
084     */
085    public static boolean compareString(CompareOperation oper, Object value1, Object value2, boolean caseInsensitive) {
086        String s1;
087        String s2;
088        if (value1 == null) {
089            return value2 == null;
090        } else {
091            if (value2 == null) {
092                return false;
093            }
094            s1 = value1.toString().trim();
095            s2 = value2.toString().trim();
096        }
097        int compare;
098        if (caseInsensitive) {
099            compare = s1.compareToIgnoreCase(s2);
100        } else {
101            compare = s1.compareTo(s2);
102        }
103        switch (oper) {
104            case LessThan:
105                if (compare < 0) {
106                    return true;
107                }
108                break;
109            case LessThanOrEqual:
110                if (compare <= 0) {
111                    return true;
112                }
113                break;
114            case Equal:
115                if (compare == 0) {
116                    return true;
117                }
118                break;
119            case NotEqual:
120                if (compare != 0) {
121                    return true;
122                }
123                break;
124            case GreaterThanOrEqual:
125                if (compare >= 0) {
126                    return true;
127                }
128                break;
129            case GreaterThan:
130                if (compare > 0) {
131                    return true;
132                }
133                break;
134            default:
135                throw new IllegalArgumentException("oper has unknown value: "+oper.name());
136        }
137        return false;
138    }
139
140    /**
141     * Compare two values.
142     *
143     * @param requireNumber   true if two numbers are required, false otherwise
144     * @param oper            the operation
145     * @param value1          left side of the comparison
146     * @param value2          right side of the comparison
147     * @param caseInsensitive true if comparison should be case insensitive;
148     *                        false otherwise
149     * @return true if values compare per _memoryOperation; false otherwise
150     */
151    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY", 
152                justification = "User explicitly requested check for equality with EQUAL case")
153    public static boolean compareNumber(boolean requireNumber, CompareOperation oper, Object value1, Object value2, boolean caseInsensitive) {
154        String s1;
155        String s2;
156        if (value1 == null) {
157            return value2 == null;
158        } else {
159            if (value2 == null) {
160                return false;
161            }
162            s1 = value1.toString().trim();
163            s2 = value2.toString().trim();
164        }
165        try {
166            double n1;
167            if (value1 instanceof Number) {
168                n1 = ((Number)value1).doubleValue();
169            } else {
170                n1 = Double.parseDouble(s1);
171            }
172            try {
173                double n2;
174                if (value2 instanceof Number) {
175                    n2 = ((Number)value2).doubleValue();
176                } else {
177                    n2 = Double.parseDouble(s2);
178                }
179                log.debug("Compare numbers: n1= {} to n2= {}", n1, n2);
180                switch (oper) // both are numbers
181                {
182                    case LessThan:
183                        return (n1 < n2);
184                    case LessThanOrEqual:
185                        return (n1 <= n2);
186                    case Equal:
187                        return (n1 == n2);
188                    case NotEqual:
189                        return (n1 != n2);
190                    case GreaterThanOrEqual:
191                        return (n1 >= n2);
192                    case GreaterThan:
193                        return (n1 > n2);
194                    default:
195                        throw new IllegalArgumentException("oper has unknown value: "+oper.name());
196                }
197            } catch (NumberFormatException nfe) {
198                if (requireNumber) throw new IllegalArgumentException(
199                        Bundle.getMessage("CompareUtil_Error_Value1IsNotANumber", value1));
200                return oper == CompareOperation.NotEqual;   // n1 is a number, n2 is not
201            }
202        } catch (NumberFormatException nfe) {
203            try {
204                Integer.parseInt(s2);
205                if (requireNumber) throw new IllegalArgumentException(
206                        Bundle.getMessage("CompareUtil_Error_Value1IsNotANumber", value1));
207                return oper == CompareOperation.NotEqual;   // n1 is not a number, n2 is
208            } catch (NumberFormatException ex) { // OK neither a number
209                if (requireNumber) throw new IllegalArgumentException(
210                        Bundle.getMessage("CompareUtil_Error_NeitherValueIsNumber", value1, value2));
211            }
212        }
213
214        // If here, neither value is a number and it's not required.
215        return compareString(oper, value1, value2, caseInsensitive);
216    }
217
218
219    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CompareUtil.class);
220
221}