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 java.math.BigDecimal;
  25 import java.math.BigInteger;
  26 
  27 import com.sun.org.apache.xerces.internal.impl.dv.InvalidDatatypeValueException;
  28 import com.sun.org.apache.xerces.internal.impl.dv.ValidationContext;
  29 import com.sun.org.apache.xerces.internal.xs.datatypes.XSDecimal;
  30 import 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  */
  41 public 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