/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.jena.datatypes.xsd.impl; import java.math.BigDecimal; import java.math.BigInteger; import org.apache.jena.datatypes.* ; import org.apache.jena.datatypes.xsd.* ; import org.apache.jena.graph.impl.LiteralLabel ; import org.apache.jena.shared.impl.JenaParameters ; /** * Base implementation for all numeric datatypes derived from * xsd:decimal. The only purpose of this place holder is * to support the isValidLiteral tests across numeric types. * * <p>Note that float and double are not included in this set. */ public class XSDBaseNumericType extends XSDDatatype { /** * Constructor. * @param typeName the name of the XSD type to be instantiated, this is * used to lookup a type definition from the Xerces schema factory. */ public XSDBaseNumericType(String typeName) { super(typeName); } /** * Constructor. * @param typeName the name of the XSD type to be instantiated, this is * used to lookup a type definition from the Xerces schema factory. * @param javaClass the java class for which this xsd type is to be * treated as the cannonical representation */ public XSDBaseNumericType(String typeName, Class<?> javaClass) { super(typeName, javaClass); } /** * Test whether the given LiteralLabel is a valid instance * of this datatype. This takes into accound typing information * as well as lexical form - for example an xsd:string is * never considered valid as an xsd:integer (even if it is * lexically legal like "1"). */ @Override public boolean isValidLiteral(LiteralLabel lit) { if (isBaseTypeCompatible(lit)) { String lex = lit.getLexicalForm(); if (JenaParameters.enableWhitespaceCheckingOfTypedLiterals) { if (lex.trim().equals(lex)) { return isValid(lit.getLexicalForm()); } else { return false; } } else { return isValid(lit.getLexicalForm()); } } else { return false; } } /** * Test whether the given object is a legal value form * of this datatype. Brute force implementation. */ @Override public boolean isValidValue(Object valueForm) { if (valueForm instanceof Number) { return isValid(unparse(valueForm)); } else { return false; } } /** * Cannonicalise a java Object value to a normal form. * Primarily used in cases such as xsd:integer to reduce * the Java object representation to the narrowest of the Number * subclasses to ensure that indexing of typed literals works. */ @Override public Object cannonicalise( Object value ) { if (value instanceof BigInteger) { return cannonicalizeInteger( (BigInteger)value ); } else if (value instanceof BigDecimal) { return cannonicalizeDecimal( (BigDecimal)value ); } return suitableInteger( ((Number)value).longValue() ); } private static final BigInteger ten = new BigInteger("10"); private static final int QUOT = 0; private static final int REM = 1; /** * Cannonicalize a big decimal */ private Object cannonicalizeDecimal(BigDecimal value) { // This could can be simplified by using toBigIntegerExact if (value.scale() > 0) { // Check if we can strip off any trailing zeros after decimal point BigInteger i = value.unscaledValue(); int limit = value.scale(); int nshift = 0; for (nshift = 0; nshift < limit; nshift++) { BigInteger[] quotRem = i.divideAndRemainder(ten); if (quotRem[REM].intValue() != 0) break; i = quotRem[QUOT]; } if (nshift > 0) { value = new BigDecimal(i, limit - nshift); if (value.scale() <= 0) { return cannonicalizeInteger( value.toBigInteger() ); } } return value; } else { return cannonicalizeInteger( value.toBigInteger() ); } } /** * Cannonicalize a big integer */ private Object cannonicalizeInteger( BigInteger value) { if (value.bitLength() > 63) { return value; } else { return suitableInteger( value.longValue() ); } } /** * Parse a lexical form of this datatype to a value * @throws DatatypeFormatException if the lexical form is not legal */ @Override public Object parse(String lexicalForm) throws DatatypeFormatException { checkWhitespace(lexicalForm); return super.parse(lexicalForm); } /** * Convert a value of this datatype to lexical form. * Certain forms are not a simple matter of java's toString on the Number object. */ @Override public String unparse(Object value) { if ( value instanceof BigDecimal ) // Avoid exponent usage. return ((BigDecimal)value).toPlainString() ; // See also for XSDfloat and XSDdouble. // Integer hierarchy is OK. return value.toString(); } /** * Check for whitespace violations. * Turned off by default. */ protected void checkWhitespace(String lexicalForm) { if (JenaParameters.enableWhitespaceCheckingOfTypedLiterals) { if ( ! lexicalForm.trim().equals(lexicalForm)) { throw new DatatypeFormatException(lexicalForm, this, "whitespace violation"); } } } /** * Compares two instances of values of the given datatype. */ @Override public boolean isEqual(LiteralLabel value1, LiteralLabel value2) { if (value1.getDatatype() instanceof XSDBaseNumericType && value2.getDatatype() instanceof XSDBaseNumericType) { Number n1 = (Number)value1.getValue(); Number n2 = (Number)value2.getValue(); // The cannonicalization step should take care of all cross-type cases, leaving // us just that equals doesn't work on BigDecimals in the way you expect if (n1 instanceof BigDecimal && n2 instanceof BigDecimal) { return ((BigDecimal)n1).compareTo((BigDecimal)n2) == 0; } return n1.equals(n2); } else { // At least one arg is not part of the integer hierarchy return false; } } }