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    public static boolean compareNumber(boolean requireNumber, CompareOperation oper, Object value1, Object value2, boolean caseInsensitive) {
152        String s1;
153        String s2;
154        if (value1 == null) {
155            return value2 == null;
156        } else {
157            if (value2 == null) {
158                return false;
159            }
160            s1 = value1.toString().trim();
161            s2 = value2.toString().trim();
162        }
163        try {
164            double n1;
165            if (value1 instanceof Number) {
166                n1 = ((Number)value1).doubleValue();
167            } else {
168                n1 = Double.parseDouble(s1);
169            }
170            try {
171                double n2;
172                if (value2 instanceof Number) {
173                    n2 = ((Number)value2).doubleValue();
174                } else {
175                    n2 = Double.parseDouble(s2);
176                }
177                log.debug("Compare numbers: n1= {} to n2= {}", n1, n2);
178                switch (oper) // both are numbers
179                {
180                    case LessThan:
181                        return (n1 < n2);
182                    case LessThanOrEqual:
183                        return (n1 <= n2);
184                    case Equal:
185                        return (n1 == n2);
186                    case NotEqual:
187                        return (n1 != n2);
188                    case GreaterThanOrEqual:
189                        return (n1 >= n2);
190                    case GreaterThan:
191                        return (n1 > n2);
192                    default:
193                        throw new IllegalArgumentException("oper has unknown value: "+oper.name());
194                }
195            } catch (NumberFormatException nfe) {
196                if (requireNumber) throw new IllegalArgumentException(
197                        Bundle.getMessage("CompareUtil_Error_Value1IsNotANumber", value1));
198                return oper == CompareOperation.NotEqual;   // n1 is a number, n2 is not
199            }
200        } catch (NumberFormatException nfe) {
201            try {
202                Integer.parseInt(s2);
203                if (requireNumber) throw new IllegalArgumentException(
204                        Bundle.getMessage("CompareUtil_Error_Value1IsNotANumber", value1));
205                return oper == CompareOperation.NotEqual;   // n1 is not a number, n2 is
206            } catch (NumberFormatException ex) { // OK neither a number
207                if (requireNumber) throw new IllegalArgumentException(
208                        Bundle.getMessage("CompareUtil_Error_NeitherValueIsNumber", value1, value2));
209            }
210        }
211
212        // If here, neither value is a number and it's not required.
213        return compareString(oper, value1, value2, caseInsensitive);
214    }
215
216
217    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CompareUtil.class);
218
219}