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 
22 package com.sun.org.apache.xerces.internal.impl.dv.xs;
23 
24 import com.sun.org.apache.xerces.internal.impl.dv.InvalidDatatypeValueException;
25 import 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  */
35 class 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 
XPrecisionDecimal(String content)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 
initD(String content)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.
canonicalToStringForHashCode(String ivalue, String fvalue, int sign, int pvalue)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
equals(Object val)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
hashCode()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          */
compareFractionalPart(XPrecisionDecimal oval)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 
truncateTrailingZeros(StringBuffer fValue, StringBuffer otherFValue)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 
compareTo(XPrecisionDecimal val)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.
compare(XPrecisionDecimal val)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          */
intComp(XPrecisionDecimal val)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          */
compareDecimal(String iValue, String fValue, String otherIValue, String otherFValue)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
toString()379         public synchronized String toString() {
380             if (canonical == null) {
381                 makeCanonical();
382             }
383             return canonical;
384         }
385 
makeCanonical()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          */
isIdentical(XPrecisionDecimal decimal)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
getAllowedFacets()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
getActualValue(String content, ValidationContext context)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
compare(Object value1, Object value2)428     public int compare(Object value1, Object value2) {
429         return ((XPrecisionDecimal)value1).compareTo((XPrecisionDecimal)value2);
430     }
431 
432     @Override
getFractionDigits(Object value)433     public int getFractionDigits(Object value) {
434         return ((XPrecisionDecimal)value).fracDigits;
435     }
436 
437     @Override
getTotalDigits(Object value)438     public int getTotalDigits(Object value) {
439         return ((XPrecisionDecimal)value).totalDigits;
440     }
441 
442     @Override
isIdentical(Object value1, Object value2)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