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 }