1/*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5/*
6 * Licensed to the Apache Software Foundation (ASF) under one or more
7 * contributor license agreements.  See the NOTICE file distributed with
8 * this work for additional information regarding copyright ownership.
9 * The ASF licenses this file to You under the Apache License, Version 2.0
10 * (the "License"); you may not use this file except in compliance with
11 * the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21
22package com.sun.org.apache.xerces.internal.impl.dv.xs;
23
24import com.sun.org.apache.xerces.internal.impl.dv.InvalidDatatypeValueException;
25import com.sun.org.apache.xerces.internal.impl.dv.ValidationContext;
26
27/**
28 * Validator for <precisionDecimal> datatype (W3C Schema 1.1)
29 *
30 * @xerces.experimental
31 *
32 * @author Ankit Pasricha, IBM
33 *
34 */
35class PrecisionDecimalDV extends TypeValidator {
36
37    static final class XPrecisionDecimal {
38
39        // sign: 0 for absent; 1 for positive values; -1 for negative values (except in case of INF, -INF)
40        int sign = 1;
41        // total digits. >= 1
42        int totalDigits = 0;
43        // integer digits when sign != 0
44        int intDigits = 0;
45        // fraction digits when sign != 0
46        int fracDigits = 0;
47        //precision
48        //int precision = 0;
49        // the string representing the integer part
50        String ivalue = "";
51        // the string representing the fraction part
52        String fvalue = "";
53
54        int pvalue = 0;
55
56
57        XPrecisionDecimal(String content) throws NumberFormatException {
58            if(content.equals("NaN")) {
59                ivalue = content;
60                sign = 0;
61            }
62            if(content.equals("+INF") || content.equals("INF") || content.equals("-INF")) {
63                ivalue = content.charAt(0) == '+' ? content.substring(1) : content;
64                return;
65            }
66            initD(content);
67        }
68
69        void initD(String content) throws NumberFormatException {
70            int len = content.length();
71            if (len == 0)
72                throw new NumberFormatException();
73
74            // these 4 variables are used to indicate where the integre/fraction
75            // parts start/end.
76            int intStart = 0, intEnd = 0, fracStart = 0, fracEnd = 0;
77
78            // Deal with leading sign symbol if present
79            if (content.charAt(0) == '+') {
80                // skip '+', so intStart should be 1
81                intStart = 1;
82            }
83            else if (content.charAt(0) == '-') {
84                intStart = 1;
85                sign = -1;
86            }
87
88            // skip leading zeroes in integer part
89            int actualIntStart = intStart;
90            while (actualIntStart < len && content.charAt(actualIntStart) == '0') {
91                actualIntStart++;
92            }
93
94            // Find the ending position of the integer part
95            for (intEnd = actualIntStart; intEnd < len && TypeValidator.isDigit(content.charAt(intEnd)); intEnd++);
96
97            // Not reached the end yet
98            if (intEnd < len) {
99                // the remaining part is not ".DDD" or "EDDD" or "eDDD", error
100                if (content.charAt(intEnd) != '.' && content.charAt(intEnd) != 'E' && content.charAt(intEnd) != 'e')
101                    throw new NumberFormatException();
102
103                if(content.charAt(intEnd) == '.') {
104                    // fraction part starts after '.', and ends at the end of the input
105                    fracStart = intEnd + 1;
106
107                    // find location of E or e (if present)
108                    // Find the ending position of the fracion part
109                    for (fracEnd = fracStart;
110                    fracEnd < len && TypeValidator.isDigit(content.charAt(fracEnd));
111                    fracEnd++);
112                }
113                else {
114                    pvalue = Integer.parseInt(content.substring(intEnd + 1, len));
115                }
116            }
117
118            // no integer part, no fraction part, error.
119            if (intStart == intEnd && fracStart == fracEnd)
120                throw new NumberFormatException();
121
122            // ignore trailing zeroes in fraction part
123            /*while (fracEnd > fracStart && content.charAt(fracEnd-1) == '0') {
124             fracEnd--;
125             }*/
126
127            // check whether there is non-digit characters in the fraction part
128            for (int fracPos = fracStart; fracPos < fracEnd; fracPos++) {
129                if (!TypeValidator.isDigit(content.charAt(fracPos)))
130                    throw new NumberFormatException();
131            }
132
133            intDigits = intEnd - actualIntStart;
134            fracDigits = fracEnd - fracStart;
135
136            if (intDigits > 0) {
137                ivalue = content.substring(actualIntStart, intEnd);
138            }
139
140            if (fracDigits > 0) {
141                fvalue = content.substring(fracStart, fracEnd);
142                if(fracEnd < len) {
143                    pvalue = Integer.parseInt(content.substring(fracEnd + 1, len));
144                }
145            }
146            totalDigits = intDigits + fracDigits;
147        }
148
149        // Construct a canonical String representation of this number
150        // for the purpose of deriving a hashCode value compliant with
151        // equals.
152        // The toString representation will be:
153        // NaN for NaN, INF for +infinity, -INF for -infinity, 0 for zero,
154        // and [1-9].[0-9]*[1-9]?(E[1-9][0-9]*)? for other numbers.
155        private static String canonicalToStringForHashCode(String ivalue, String fvalue, int sign, int pvalue) {
156            if ("NaN".equals(ivalue)) {
157                return "NaN";
158            }
159            if ("INF".equals(ivalue)) {
160                return sign < 0 ? "-INF" : "INF";
161            }
162            final StringBuilder builder = new StringBuilder();
163            final int ilen = ivalue.length();
164            final int flen0 = fvalue.length();
165            int lastNonZero;
166            for (lastNonZero = flen0; lastNonZero > 0 ; lastNonZero--) {
167                if (fvalue.charAt(lastNonZero -1 ) != '0') break;
168            }
169            final int flen = lastNonZero;
170            int iStart;
171            int exponent = pvalue;
172            for (iStart = 0; iStart < ilen; iStart++) {
173                if (ivalue.charAt(iStart) != '0') break;
174            }
175            int fStart = 0;
176            if (iStart < ivalue.length()) {
177                builder.append(sign == -1 ? "-" : "");
178                builder.append(ivalue.charAt(iStart));
179                iStart++;
180            } else {
181                if (flen > 0) {
182                    for (fStart = 0; fStart < flen; fStart++) {
183                        if (fvalue.charAt(fStart) != '0') break;
184                    }
185                    if (fStart < flen) {
186                        builder.append(sign == -1 ? "-" : "");
187                        builder.append(fvalue.charAt(fStart));
188                        exponent -= ++fStart;
189                    } else {
190                        return "0";
191                    }
192                } else {
193                    return "0";
194                }
195            }
196
197            if (iStart < ilen || fStart < flen) {
198                builder.append('.');
199            }
200            while (iStart < ilen) {
201                builder.append(ivalue.charAt(iStart++));
202                exponent++;
203            }
204            while (fStart < flen) {
205                builder.append(fvalue.charAt(fStart++));
206            }
207            if (exponent != 0) {
208                builder.append("E").append(exponent);
209            }
210            return builder.toString();
211        }
212
213        @Override
214        public boolean equals(Object val) {
215            if (val == this)
216                return true;
217
218            if (!(val instanceof XPrecisionDecimal))
219                return false;
220            XPrecisionDecimal oval = (XPrecisionDecimal)val;
221
222            return this.compareTo(oval) == EQUAL;
223        }
224
225        @Override
226        public int hashCode() {
227            // There's nothing else we can use easily, because equals could
228            // return true for widely different representation of the
229            // same number - and we don't have any canonical representation.
230            // The problem here is that we must ensure that if two numbers
231            // are equals then their hash code must also be equals.
232            // hashCode for 1.01E1 should be the same as hashCode for 0.101E2
233            // So we call cannonicalToStringForHashCode - which implements an
234            // algorithm that invents a normalized string representation
235            // for this number, and we return a hash for that.
236            return canonicalToStringForHashCode(ivalue, fvalue, sign, pvalue).hashCode();
237        }
238
239        /**
240         * @return
241         */
242        private int compareFractionalPart(XPrecisionDecimal oval) {
243            if(fvalue.equals(oval.fvalue))
244                return EQUAL;
245
246            StringBuffer temp1 = new StringBuffer(fvalue);
247            StringBuffer temp2 = new StringBuffer(oval.fvalue);
248
249            truncateTrailingZeros(temp1, temp2);
250            return temp1.toString().compareTo(temp2.toString());
251        }
252
253        private void truncateTrailingZeros(StringBuffer fValue, StringBuffer otherFValue) {
254            for(int i = fValue.length() - 1;i >= 0; i--)
255                if(fValue.charAt(i) == '0')
256                    fValue.deleteCharAt(i);
257                else
258                    break;
259
260            for(int i = otherFValue.length() - 1;i >= 0; i--)
261                if(otherFValue.charAt(i) == '0')
262                    otherFValue.deleteCharAt(i);
263                else
264                    break;
265        }
266
267        public int compareTo(XPrecisionDecimal val) {
268
269            // seen NaN
270            if(sign == 0)
271                return INDETERMINATE;
272
273            //INF is greater than everything and equal to itself
274            if(ivalue.equals("INF") || val.ivalue.equals("INF")) {
275                if(ivalue.equals(val.ivalue))
276                    return EQUAL;
277                else if(ivalue.equals("INF"))
278                    return GREATER_THAN;
279                return LESS_THAN;
280            }
281
282            //-INF is smaller than everything and equal itself
283            if(ivalue.equals("-INF") || val.ivalue.equals("-INF")) {
284                if(ivalue.equals(val.ivalue))
285                    return EQUAL;
286                else if(ivalue.equals("-INF"))
287                    return LESS_THAN;
288                return GREATER_THAN;
289            }
290
291            if (sign != val.sign)
292                return sign > val.sign ? GREATER_THAN : LESS_THAN;
293
294            return sign * compare(val);
295        }
296
297        // To enable comparison - the exponent part of the decimal will be limited
298        // to the max value of int.
299        private int compare(XPrecisionDecimal val) {
300
301            if(pvalue != 0 || val.pvalue != 0) {
302                if(pvalue == val.pvalue)
303                    return intComp(val);
304                else {
305
306                    if(intDigits + pvalue != val.intDigits + val.pvalue)
307                        return intDigits + pvalue > val.intDigits + val.pvalue ? GREATER_THAN : LESS_THAN;
308
309                    //otherwise the 2 combined values are the same
310                    if(pvalue > val.pvalue) {
311                        int expDiff = pvalue - val.pvalue;
312                        StringBuffer buffer = new StringBuffer(ivalue);
313                        StringBuffer fbuffer = new StringBuffer(fvalue);
314                        for(int i = 0;i < expDiff; i++) {
315                            if(i < fracDigits) {
316                                buffer.append(fvalue.charAt(i));
317                                fbuffer.deleteCharAt(i);
318                            }
319                            else
320                                buffer.append('0');
321                        }
322                        return compareDecimal(buffer.toString(), val.ivalue, fbuffer.toString(), val.fvalue);
323                    }
324                    else {
325                        int expDiff = val.pvalue - pvalue;
326                        StringBuffer buffer = new StringBuffer(val.ivalue);
327                        StringBuffer fbuffer = new StringBuffer(val.fvalue);
328                        for(int i = 0;i < expDiff; i++) {
329                            if(i < val.fracDigits) {
330                                buffer.append(val.fvalue.charAt(i));
331                                fbuffer.deleteCharAt(i);
332                            }
333                            else
334                                buffer.append('0');
335                        }
336                        return compareDecimal(ivalue, buffer.toString(), fvalue, fbuffer.toString());
337                    }
338                }
339            }
340            else {
341                return intComp(val);
342            }
343        }
344
345        /**
346         * @param val
347         * @return
348         */
349        private int intComp(XPrecisionDecimal val) {
350            if (intDigits != val.intDigits)
351                return intDigits > val.intDigits ? GREATER_THAN : LESS_THAN;
352
353            return compareDecimal(ivalue, val.ivalue, fvalue, val.fvalue);
354        }
355
356        /**
357         * @param val
358         * @return
359         */
360        private int compareDecimal(String iValue, String fValue, String otherIValue, String otherFValue) {
361            int ret = iValue.compareTo(otherIValue);
362            if (ret != 0)
363                return ret > 0 ? GREATER_THAN : LESS_THAN;
364
365            if(fValue.equals(otherFValue))
366                return EQUAL;
367
368            StringBuffer temp1=new StringBuffer(fValue);
369            StringBuffer temp2=new StringBuffer(otherFValue);
370
371            truncateTrailingZeros(temp1, temp2);
372            ret = temp1.toString().compareTo(temp2.toString());
373            return ret == 0 ? EQUAL : (ret > 0 ? GREATER_THAN : LESS_THAN);
374        }
375
376        private String canonical;
377
378        @Override
379        public synchronized String toString() {
380            if (canonical == null) {
381                makeCanonical();
382            }
383            return canonical;
384        }
385
386        private void makeCanonical() {
387            // REVISIT: to be determined by working group
388            canonical = "TBD by Working Group";
389        }
390
391        /**
392         * @param decimal
393         * @return
394         */
395        public boolean isIdentical(XPrecisionDecimal decimal) {
396            if(ivalue.equals(decimal.ivalue) && (ivalue.equals("INF") || ivalue.equals("-INF") || ivalue.equals("NaN")))
397                return true;
398
399            if(sign == decimal.sign && intDigits == decimal.intDigits && fracDigits == decimal.fracDigits && pvalue == decimal.pvalue
400                    && ivalue.equals(decimal.ivalue) && fvalue.equals(decimal.fvalue))
401                return true;
402            return false;
403        }
404
405    }
406    /* (non-Javadoc)
407     * @see com.sun.org.apache.xerces.internal.impl.dv.xs.TypeValidator#getAllowedFacets()
408     */
409    @Override
410    public short getAllowedFacets() {
411        return ( XSSimpleTypeDecl.FACET_PATTERN | XSSimpleTypeDecl.FACET_WHITESPACE | XSSimpleTypeDecl.FACET_ENUMERATION |XSSimpleTypeDecl.FACET_MAXINCLUSIVE |XSSimpleTypeDecl.FACET_MININCLUSIVE | XSSimpleTypeDecl.FACET_MAXEXCLUSIVE  | XSSimpleTypeDecl.FACET_MINEXCLUSIVE | XSSimpleTypeDecl.FACET_TOTALDIGITS | XSSimpleTypeDecl.FACET_FRACTIONDIGITS);
412    }
413
414    /* (non-Javadoc)
415     * @see com.sun.org.apache.xerces.internal.impl.dv.xs.TypeValidator#getActualValue(java.lang.String, com.sun.org.apache.xerces.internal.impl.dv.ValidationContext)
416     */
417    @Override
418    public Object getActualValue(String content, ValidationContext context)
419    throws InvalidDatatypeValueException {
420        try {
421            return new XPrecisionDecimal(content);
422        } catch (NumberFormatException nfe) {
423            throw new InvalidDatatypeValueException("cvc-datatype-valid.1.2.1", new Object[]{content, "precisionDecimal"});
424        }
425    }
426
427    @Override
428    public int compare(Object value1, Object value2) {
429        return ((XPrecisionDecimal)value1).compareTo((XPrecisionDecimal)value2);
430    }
431
432    @Override
433    public int getFractionDigits(Object value) {
434        return ((XPrecisionDecimal)value).fracDigits;
435    }
436
437    @Override
438    public int getTotalDigits(Object value) {
439        return ((XPrecisionDecimal)value).totalDigits;
440    }
441
442    @Override
443    public boolean isIdentical(Object value1, Object value2) {
444        if(!(value2 instanceof XPrecisionDecimal) || !(value1 instanceof XPrecisionDecimal))
445            return false;
446        return ((XPrecisionDecimal)value1).isIdentical((XPrecisionDecimal)value2);
447    }
448}
449