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 java.math.BigDecimal;
25import java.math.BigInteger;
26
27import com.sun.org.apache.xerces.internal.impl.dv.InvalidDatatypeValueException;
28import com.sun.org.apache.xerces.internal.impl.dv.ValidationContext;
29import com.sun.org.apache.xerces.internal.xs.datatypes.XSDecimal;
30import java.util.Objects;
31
32/**
33 * Represent the schema type "decimal"
34 *
35 * @xerces.internal
36 *
37 * @author Neeraj Bajaj, Sun Microsystems, inc.
38 * @author Sandy Gao, IBM
39 *
40 */
41public class DecimalDV extends TypeValidator {
42
43    @Override
44    public final short getAllowedFacets(){
45        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);
46    }
47
48    @Override
49    public Object getActualValue(String content, ValidationContext context) throws InvalidDatatypeValueException {
50        try {
51            return new XDecimal(content);
52        } catch (NumberFormatException nfe) {
53            throw new InvalidDatatypeValueException("cvc-datatype-valid.1.2.1", new Object[]{content, "decimal"});
54        }
55    }
56
57    @Override
58    public final int compare(Object value1, Object value2){
59        return ((XDecimal)value1).compareTo((XDecimal)value2);
60    }
61
62    @Override
63    public final int getTotalDigits(Object value){
64        return ((XDecimal)value).totalDigits;
65    }
66
67    @Override
68    public final int getFractionDigits(Object value){
69        return ((XDecimal)value).fracDigits;
70    }
71
72    // Avoid using the heavy-weight java.math.BigDecimal
73    static final class XDecimal implements XSDecimal {
74        // sign: 0 for vlaue 0; 1 for positive values; -1 for negative values
75        int sign = 1;
76        // total digits. >= 1
77        int totalDigits = 0;
78        // integer digits when sign != 0
79        int intDigits = 0;
80        // fraction digits when sign != 0
81        int fracDigits = 0;
82        // the string representing the integer part
83        String ivalue = "";
84        // the string representing the fraction part
85        String fvalue = "";
86        // whether the canonical form contains decimal point
87        boolean integer = false;
88
89        XDecimal(String content) throws NumberFormatException {
90            initD(content);
91        }
92        XDecimal(String content, boolean integer) throws NumberFormatException {
93            if (integer)
94                initI(content);
95            else
96                initD(content);
97        }
98        void initD(String content) throws NumberFormatException {
99            int len = content.length();
100            if (len == 0)
101                throw new NumberFormatException();
102
103            // these 4 variables are used to indicate where the integre/fraction
104            // parts start/end.
105            int intStart = 0, intEnd = 0, fracStart = 0, fracEnd = 0;
106
107            // Deal with leading sign symbol if present
108            if (content.charAt(0) == '+') {
109                // skip '+', so intStart should be 1
110                intStart = 1;
111            }
112            else if (content.charAt(0) == '-') {
113                // keep '-', so intStart is stil 0
114                intStart = 1;
115                sign = -1;
116            }
117
118            // skip leading zeroes in integer part
119            int actualIntStart = intStart;
120            while (actualIntStart < len && content.charAt(actualIntStart) == '0') {
121                actualIntStart++;
122            }
123
124            // Find the ending position of the integer part
125            for (intEnd = actualIntStart;
126                 intEnd < len && TypeValidator.isDigit(content.charAt(intEnd));
127                 intEnd++);
128
129            // Not reached the end yet
130            if (intEnd < len) {
131                // the remaining part is not ".DDD", error
132                if (content.charAt(intEnd) != '.')
133                    throw new NumberFormatException();
134
135                // fraction part starts after '.', and ends at the end of the input
136                fracStart = intEnd + 1;
137                fracEnd = len;
138            }
139
140            // no integer part, no fraction part, error.
141            if (intStart == intEnd && fracStart == fracEnd)
142                throw new NumberFormatException();
143
144            // ignore trailing zeroes in fraction part
145            while (fracEnd > fracStart && content.charAt(fracEnd-1) == '0') {
146                fracEnd--;
147            }
148
149            // check whether there is non-digit characters in the fraction part
150            for (int fracPos = fracStart; fracPos < fracEnd; fracPos++) {
151                if (!TypeValidator.isDigit(content.charAt(fracPos)))
152                    throw new NumberFormatException();
153            }
154
155            intDigits = intEnd - actualIntStart;
156            fracDigits = fracEnd - fracStart;
157            totalDigits = intDigits + fracDigits;
158
159            if (intDigits > 0) {
160                ivalue = content.substring(actualIntStart, intEnd);
161                if (fracDigits > 0)
162                    fvalue = content.substring(fracStart, fracEnd);
163            }
164            else {
165                if (fracDigits > 0) {
166                    fvalue = content.substring(fracStart, fracEnd);
167                }
168                else {
169                    // ".00", treat it as "0"
170                    sign = 0;
171                }
172            }
173        }
174        void initI(String content) throws NumberFormatException {
175            int len = content.length();
176            if (len == 0)
177                throw new NumberFormatException();
178
179            // these 2 variables are used to indicate where the integre start/end.
180            int intStart = 0, intEnd = 0;
181
182            // Deal with leading sign symbol if present
183            if (content.charAt(0) == '+') {
184                // skip '+', so intStart should be 1
185                intStart = 1;
186            }
187            else if (content.charAt(0) == '-') {
188                // keep '-', so intStart is stil 0
189                intStart = 1;
190                sign = -1;
191            }
192
193            // skip leading zeroes in integer part
194            int actualIntStart = intStart;
195            while (actualIntStart < len && content.charAt(actualIntStart) == '0') {
196                actualIntStart++;
197            }
198
199            // Find the ending position of the integer part
200            for (intEnd = actualIntStart;
201                 intEnd < len && TypeValidator.isDigit(content.charAt(intEnd));
202                 intEnd++);
203
204            // Not reached the end yet, error
205            if (intEnd < len)
206                throw new NumberFormatException();
207
208            // no integer part, error.
209            if (intStart == intEnd)
210                throw new NumberFormatException();
211
212            intDigits = intEnd - actualIntStart;
213            fracDigits = 0;
214            totalDigits = intDigits;
215
216            if (intDigits > 0) {
217                ivalue = content.substring(actualIntStart, intEnd);
218            }
219            else {
220                // "00", treat it as "0"
221                sign = 0;
222            }
223
224            integer = true;
225        }
226
227        @Override
228        public boolean equals(Object val) {
229            if (val == this)
230                return true;
231
232            if (!(val instanceof XDecimal))
233                return false;
234            XDecimal oval = (XDecimal)val;
235
236            if (sign != oval.sign)
237               return false;
238            if (sign == 0)
239                return true;
240
241            return intDigits == oval.intDigits && fracDigits == oval.fracDigits &&
242                   ivalue.equals(oval.ivalue) && fvalue.equals(oval.fvalue);
243        }
244
245        @Override
246        public int hashCode() {
247            int hash = 7;
248            hash = 17 * hash + this.sign;
249            if (this.sign == 0) return hash;
250            hash = 17 * hash + this.intDigits;
251            hash = 17 * hash + this.fracDigits;
252            hash = 17 * hash + Objects.hashCode(this.ivalue);
253            hash = 17 * hash + Objects.hashCode(this.fvalue);
254            return hash;
255        }
256
257        public int compareTo(XDecimal val) {
258            if (sign != val.sign)
259                return sign > val.sign ? 1 : -1;
260            if (sign == 0)
261                return 0;
262            return sign * intComp(val);
263        }
264        private int intComp(XDecimal val) {
265            if (intDigits != val.intDigits)
266                return intDigits > val.intDigits ? 1 : -1;
267            int ret = ivalue.compareTo(val.ivalue);
268            if (ret != 0)
269                return ret > 0 ? 1 : -1;;
270            ret = fvalue.compareTo(val.fvalue);
271            return ret == 0 ? 0 : (ret > 0 ? 1 : -1);
272        }
273
274        private String canonical;
275        @Override
276        public synchronized String toString() {
277            if (canonical == null) {
278                makeCanonical();
279            }
280            return canonical;
281        }
282
283        private void makeCanonical() {
284            if (sign == 0) {
285                if (integer)
286                    canonical = "0";
287                else
288                    canonical = "0.0";
289                return;
290            }
291            if (integer && sign > 0) {
292                canonical = ivalue;
293                return;
294            }
295            // for -0.1, total digits is 1, so we need 3 extra spots
296            final StringBuilder buffer = new StringBuilder(totalDigits+3);
297            if (sign == -1)
298                buffer.append('-');
299            if (intDigits != 0)
300                buffer.append(ivalue);
301            else
302                buffer.append('0');
303            if (!integer) {
304                buffer.append('.');
305                if (fracDigits != 0) {
306                    buffer.append(fvalue);
307                }
308                else {
309                    buffer.append('0');
310                }
311            }
312            canonical = buffer.toString();
313        }
314
315        @Override
316        public BigDecimal getBigDecimal() {
317            if (sign == 0) {
318                return new BigDecimal(BigInteger.ZERO);
319            }
320            return new BigDecimal(toString());
321        }
322
323        @Override
324        public BigInteger getBigInteger() throws NumberFormatException {
325            if (fracDigits != 0) {
326                throw new NumberFormatException();
327            }
328            if (sign == 0) {
329                return BigInteger.ZERO;
330            }
331            if (sign == 1) {
332                return new BigInteger(ivalue);
333            }
334            return new BigInteger("-" + ivalue);
335        }
336
337        @Override
338        public long getLong() throws NumberFormatException {
339            if (fracDigits != 0) {
340                throw new NumberFormatException();
341            }
342            if (sign == 0) {
343                return 0L;
344            }
345            if (sign == 1) {
346                return Long.parseLong(ivalue);
347            }
348            return Long.parseLong("-" + ivalue);
349        }
350
351        @Override
352        public int getInt() throws NumberFormatException {
353            if (fracDigits != 0) {
354                throw new NumberFormatException();
355            }
356            if (sign == 0) {
357                return 0;
358            }
359            if (sign == 1) {
360                return Integer.parseInt(ivalue);
361            }
362            return Integer.parseInt("-" + ivalue);
363        }
364
365        @Override
366        public short getShort() throws NumberFormatException {
367            if (fracDigits != 0) {
368                throw new NumberFormatException();
369            }
370            if (sign == 0) {
371                return 0;
372            }
373            if (sign == 1) {
374                return Short.parseShort(ivalue);
375            }
376            return Short.parseShort("-" + ivalue);
377        }
378
379        @Override
380        public byte getByte() throws NumberFormatException {
381            if (fracDigits != 0) {
382                throw new NumberFormatException();
383            }
384            if (sign == 0) {
385                return 0;
386            }
387            if (sign == 1) {
388                return Byte.parseByte(ivalue);
389            }
390            return Byte.parseByte("-" + ivalue);
391        }
392    }
393} // class DecimalDV
394