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 
  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 }