blob: 788c225be5499864e0e817e7893f667703c0fb94 [file] [log] [blame]
/*
* 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 account 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;
}
}
}