/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.query.processor.xml; import java.math.BigInteger; import java.sql.Timestamp; import java.util.HashMap; import java.util.Map; import javax.xml.transform.TransformerException; import net.sf.saxon.value.DateTimeValue; import net.sf.saxon.value.GYearMonthValue; import org.teiid.api.exception.query.FunctionExecutionException; import org.teiid.core.types.DataTypeManager; import org.teiid.core.types.TransformationException; import org.teiid.query.QueryPlugin; import org.teiid.query.function.FunctionMethods; import org.teiid.query.function.source.XMLSystemFunctions; import org.teiid.query.util.CommandContext; /** * This class will make a minimal effort to output xsd formatted values for a given * built-in type. It will not attempt to narrow or otherwise fit most values into * their output space (months can be greater than 12, nonNegative numbers can be * negative, etc.) */ public final class XMLValueTranslator { private static String NEGATIVE_INFINITY = "-INF"; //$NON-NLS-1$ private static String POSITIVE_INFINITY = "INF"; //$NON-NLS-1$ private static String GMONTHDAY_FORMAT = "--MM-dd"; //$NON-NLS-1$ private static String GYEAR_FORMAT = "0000"; //$NON-NLS-1$ public static final String DATETIME = "dateTime"; //$NON-NLS-1$ public static final String DOUBLE = "double"; //$NON-NLS-1$ public static final String FLOAT = "float"; //$NON-NLS-1$ public static final String GDAY = "gDay"; //$NON-NLS-1$ public static final String GMONTH = "gMonth"; //$NON-NLS-1$ public static final String GMONTHDAY = "gMonthDay"; //$NON-NLS-1$ public static final String GYEAR = "gYear"; //$NON-NLS-1$ public static final String GYEARMONTH = "gYearMonth"; //$NON-NLS-1$ public static final String STRING = "string"; //$NON-NLS-1$ private static final Map<String, Integer> TYPE_CODE_MAP; private static final int DATETIME_CODE = 0; private static final int DOUBLE_CODE = 1; private static final int FLOAT_CODE = 2; private static final int GDAY_CODE = 3; private static final int GMONTH_CODE = 4; private static final int GMONTHDAY_CODE = 5; private static final int GYEAR_CODE = 6; private static final int GYEARMONTH_CODE = 7; static { TYPE_CODE_MAP = new HashMap<String, Integer>(20); TYPE_CODE_MAP.put(DATETIME, new Integer(DATETIME_CODE)); TYPE_CODE_MAP.put(DOUBLE, new Integer(DOUBLE_CODE)); TYPE_CODE_MAP.put(FLOAT, new Integer(FLOAT_CODE)); TYPE_CODE_MAP.put(GDAY, new Integer(GDAY_CODE)); TYPE_CODE_MAP.put(GMONTH, new Integer(GMONTH_CODE)); TYPE_CODE_MAP.put(GMONTHDAY, new Integer(GMONTHDAY_CODE)); TYPE_CODE_MAP.put(GYEAR, new Integer(GYEAR_CODE)); TYPE_CODE_MAP.put(GYEARMONTH, new Integer(GYEARMONTH_CODE)); } /** * Translate the value object coming from the mapping class into the string that will be * placed in the XML document for a tag. Usually, the value.toString() method is just called * to translate to a string. In some special cases, the runtimeType and builtInType * will be used to determine a custom translation to string where the Java object toString() * does not comply with the XML Schema spec. * * NOTE: date objects are not checked for years less than 1 * * @param value The value * @param runtimeType The runtime type * @param builtInType The design-time atomic built-in type (or null if none) * @return String representing the value * @throws FunctionExecutionException * @throws TransformationException * @since 5.0 */ public static String translateToXMLValue(Object value, Class<?> runtimeType, String builtInType, CommandContext context) throws FunctionExecutionException, TransformationException { if (value == null) { return null; } Integer typeCode = TYPE_CODE_MAP.get(builtInType); String valueStr = null; if (builtInType == null || typeCode == null || runtimeType == DataTypeManager.DefaultDataClasses.STRING || STRING.equals(builtInType)) { valueStr = defaultTranslation(value); } else { int type = typeCode.intValue(); switch (type) { case DATETIME_CODE: try { valueStr = XMLSystemFunctions.convertToAtomicValue(value).getStringValue(); } catch (TransformerException e) { throw new TransformationException(QueryPlugin.Event.TEIID30208, e, e.getMessage()); } break; case DOUBLE_CODE: valueStr = doubleToDouble((Double)value); break; case FLOAT_CODE: valueStr = floatToFloat((Float)value); break; case GDAY_CODE: valueStr = bigIntegerTogDay((BigInteger)value); break; case GMONTH_CODE: valueStr = bigIntegerTogMonth((BigInteger)value); break; case GMONTHDAY_CODE: valueStr = FunctionMethods.format(context, (Timestamp)value, GMONTHDAY_FORMAT); break; case GYEAR_CODE: valueStr = FunctionMethods.format(context, (BigInteger)value, GYEAR_FORMAT); break; case GYEARMONTH_CODE: DateTimeValue dtv; try { dtv = ((DateTimeValue)XMLSystemFunctions.convertToAtomicValue(value)); } catch (TransformerException e) { throw new TransformationException(QueryPlugin.Event.TEIID30209, e, e.getMessage()); } valueStr = new GYearMonthValue(dtv.getYear(), dtv.getMonth(), dtv.getTimezoneInMinutes(), true).getStringValue(); break; default: valueStr = defaultTranslation(value); break; } } //Per defects 11789, 14905, 15117 if (valueStr != null && valueStr.length()==0){ valueStr = null; } return valueStr; } /** * Translate any non-null value to a string by using the Object toString() method. * This works in any case where the Java string representation of an object is the * same as the expected XML Schema output form. * * @param value Value returned from a mapping class * @return String content to put in XML output * @throws TransformationException * @since 5.0 */ static String defaultTranslation(Object value) throws TransformationException { return (String)DataTypeManager.transformValue(value, DataTypeManager.DefaultDataClasses.STRING); } /** * Translate runtime float to xs:float string value. The normal Java representation * for floats is fine except the strings used for positive and negative infinity * are different. * * @param f Runtime float * @return String representing xs:float * @throws TransformationException * @since 5.0 */ static String floatToFloat(Float f) throws TransformationException { if(f.floatValue() == Float.NEGATIVE_INFINITY) { return NEGATIVE_INFINITY; } else if(f.floatValue() == Float.POSITIVE_INFINITY) { return POSITIVE_INFINITY; } return defaultTranslation(f); } /** * Translate runtime double to xs:double string value. The normal Java representation * for doubles is fine except the strings used for positive and negative infinity * are different. * * @param d Runtime double * @return String representing xs:double * @throws TransformationException * @since 5.0 */ static String doubleToDouble(Double d) throws TransformationException { if(d.doubleValue() == Double.NEGATIVE_INFINITY) { return NEGATIVE_INFINITY; } else if(d.doubleValue() == Double.POSITIVE_INFINITY) { return POSITIVE_INFINITY; } return defaultTranslation(d); } /** * gMonths out of the valid range are returned in tact. * @param value * @return * @since 5.0 */ static String bigIntegerTogMonth(BigInteger value) { long month = value.longValue(); if(month < 10) { // Add leading 0 return "--0" + month; //$NON-NLS-1$ } // Don't need leading 0 return "--" + month; //$NON-NLS-1$ } /** * gDays out of the valid range are returned in tact. * @param value * @return * @since 5.0 */ static String bigIntegerTogDay(BigInteger value) { long day = value.longValue(); if(day < 10) { // Add leading 0 return "---0" + day; //$NON-NLS-1$ } // Don't need leading 0 return "---" + day; //$NON-NLS-1$ } }