/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (XMLAdapter.java) is part of project Time4J.
*
* Time4J 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.
*
* Time4J 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 Time4J. If not, see <http://www.gnu.org/licenses/>.
* -----------------------------------------------------------------------
*/
package net.time4j.xml;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.ParseException;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import net.time4j.Duration;
import net.time4j.IsoUnit;
import net.time4j.Moment;
import net.time4j.PlainDate;
import net.time4j.PlainTime;
import net.time4j.PlainTimestamp;
import net.time4j.SI;
import net.time4j.TemporalType;
import net.time4j.ZonalDateTime;
import net.time4j.engine.ChronoDisplay;
import net.time4j.engine.ChronoException;
import net.time4j.scale.LeapSeconds;
import net.time4j.tz.ZonalOffset;
import static java.math.RoundingMode.UNNECESSARY;
/**
* <p>Serves as bridge to temporal types in XML-related Java.</p>
*
* <p>All singleton instances are defined as static constants and are
* <i>immutable</i>.</p>
*
* @param <S> source type in XML-Java
* @param <T> target type in Time4J
* @author Meno Hochschild
* @since 3.0
*/
/*[deutsch]
* <p>Dient als Brücke zu Datums- und Zeittypen aus den
* XML-Bibliotheken von Java. </p>
*
* <p>Alle Singleton-Instanzen sind als statische Konstanten definiert und
* unveränderlich (<i>immutable</i>). </p>
*
* @param <S> source type in XML-Java
* @param <T> target type in Time4J
* @author Meno Hochschild
* @since 3.0
*/
public abstract class XMLAdapter<S, T>
extends TemporalType<S, T> {
//~ Statische Felder/Initialisierungen --------------------------------
private static final int MIO = 1000000;
private static final int MRD = 1000000000;
private static final BigDecimal MRD_D = BigDecimal.valueOf(MRD);
private static final BigInteger MRD_I = BigInteger.valueOf(MRD);
private static final XmlDateTimeRule XML_TIMESTAMP = new XmlDateTimeRule();
/**
* <p>Bridge between a XML-date according to {@code xsd:date}
* and the type {@code PlainDate}. </p>
*
* <p>Example: </p>
*
* <pre>
* XMLGregorianCalendar xmlGregCal =
* DatatypeFactory.newInstance().newXMLGregorianCalendarDate(
* 2014, 2, 28, 60); // here with optional offset
* PlainDate date = XMLAdapter.XML_DATE.translate(xmlGregCal);
* System.out.println(date);
* // output: 2014-02-28
* </pre>
*
* @since 3.0
*/
/*[deutsch]
* <p>Brücke zwischen einem XML-Datum entsprechend
* {@code xsd:date} und dem Typ {@code PlainDate}. </p>
*
* <p>Beispiel: </p>
*
* <pre>
* XMLGregorianCalendar xmlGregCal =
* DatatypeFactory.newInstance().newXMLGregorianCalendarDate(
* 2014, 2, 28, 60); // hier mit optionalem Offset
* PlainDate date = XMLAdapter.XML_DATE.translate(xmlGregCal);
* System.out.println(date);
* // Ausgabe: 2014-02-28
* </pre>
*
* @since 3.0
*/
public static final XMLAdapter<XMLGregorianCalendar, PlainDate> XML_DATE =
new XmlDateRule();
/**
* <p>Bridge between a XML-time according to {@code xsd:time}
* and the type {@code PlainTime}. </p>
*
* <p>Example: </p>
*
* <pre>
* XMLGregorianCalendar xmlGregCal =
* DatatypeFactory.newInstance().newXMLGregorianCalendarTime(
* 21, 45, 30, 0, 60); // here with optional offset
* PlainTime time = XMLAdapter.XML_TIME.translate(xmlGregCal);
* System.out.println(time);
* // output: T21:45:30
* </pre>
*
* <p>Note: The special value T24:00 (midnight at end of day) is mapped to
* T00:00 in the value space of {@code XMLGregorianCalendar}. </p>
*
* @since 3.0
*/
/*[deutsch]
* <p>Brücke zwischen einer XML-Uhrzeit entsprechend
* {@code xsd:time} und dem Typ {@code PlainTime}. </p>
*
* <p>Beispiel: </p>
*
* <pre>
* XMLGregorianCalendar xmlGregCal =
* DatatypeFactory.newInstance().newXMLGregorianCalendarTime(
* 21, 45, 30, 0, 60); // here with optional offset
* PlainTime time = XMLAdapter.XML_TIME.translate(xmlGregCal);
* System.out.println(time);
* // Ausgabe: T21:45:30
* </pre>
*
* <p>Hinweis: Der Spezialwert T24:00 (Mitternacht am Ende des Tages) wird auf T00:00
* im Wertraum von {@code XMLGregorianCalendar} abgebildet. </p>
*
* @since 3.0
*/
public static final XMLAdapter<XMLGregorianCalendar, PlainTime> XML_TIME =
new XmlTimeRule();
/**
* <p>Bridge between a XML-timestamp according to {@code xsd:dateTime}
* (without timezone-offset) and the type {@code PlainTimestamp}. </p>
*
* <p>Example: </p>
*
* <pre>
* XMLGregorianCalendar xmlGregCal =
* DatatypeFactory.newInstance().newXMLGregorianCalendar(
* 2014, 2, 28, 14, 45, 30, 0, 60);
* PlainTimestamp tsp = XMLAdapter.XML_DATE_TIME.translate(xmlGregCal);
* System.out.println(tsp);
* // output: 2014-02-28T14:45:30
* </pre>
*
* @since 3.0
*/
/*[deutsch]
* <p>Brücke zwischen einem XML-Zeitstempel entsprechend
* {@code xsd:dateTime} ohne Zeitzonen-Offset und dem Typ
* {@code PlainTimestamp}. </p>
*
* <p>Beispiel: </p>
*
* <pre>
* XMLGregorianCalendar xmlGregCal =
* DatatypeFactory.newInstance().newXMLGregorianCalendar(
* 2014, 2, 28, 14, 45, 30, 0, 60);
* PlainTimestamp tsp = XMLAdapter.XML_DATE_TIME.translate(xmlGregCal);
* System.out.println(tsp);
* // Ausgabe: 2014-02-28T14:45:30
* </pre>
*
* @since 3.0
*/
public static final XMLAdapter<XMLGregorianCalendar, PlainTimestamp> XML_DATE_TIME =
XML_TIMESTAMP;
/**
* <p>Bridge between a XML-timestamp according to {@code xsd:dateTime}
* inclusive timezone-offset and the type {@code ZonalDateTime}. </p>
*
* <p>Example: </p>
*
* <pre>
* XMLGregorianCalendar xmlGregCal =
* DatatypeFactory.newInstance().newXMLGregorianCalendar(
* 2014, 2, 28, 14, 45, 30, 0, 60);
* ZonalDateTime zdt = XMLAdapter.XML_DATE_TIME_OFFSET.translate(xmlGregCal);
* System.out.println(zdt.print(Iso8601Format.EXTENDED_DATE_TIME_OFFSET));
* // output: 2014-02-28T14:45:30+01:00
* </pre>
*
* @since 3.0
*/
/*[deutsch]
* <p>Brücke zwischen einem XML-Zeitstempel entsprechend
* {@code xsd:dateTime} inklusive Zeitzonen-Offset und dem Typ
* {@code ZonalDateTime}. </p>
*
* <p>Beispiel: </p>
*
* <pre>
* XMLGregorianCalendar xmlGregCal =
* DatatypeFactory.newInstance().newXMLGregorianCalendar(
* 2014, 2, 28, 14, 45, 30, 0, 60);
* ZonalDateTime zdt = XMLAdapter.XML_DATE_TIME_OFFSET.translate(xmlGregCal);
* System.out.println(zdt.print(Iso8601Format.EXTENDED_DATE_TIME_OFFSET));
* // Ausgabe: 2014-02-28T14:45:30+01:00
* </pre>
*
* @since 3.0
*/
public static final XMLAdapter<XMLGregorianCalendar, ZonalDateTime> XML_DATE_TIME_OFFSET =
new XmlDateTimeOffsetRule();
/**
* <p>Bridge between a XML-duration according to {@code xsd:duration}
* and the Time4J-type {@code Duration}. </p>
*
* @since 3.0
*/
/*[deutsch]
* <p>Brücke zwischen einer XML-Dauer entsprechend
* {@code xsd:duration} und dem Time4J-Typ {@code Duration}. </p>
*
* @since 3.0
*/
public static final XMLAdapter<javax.xml.datatype.Duration, Duration<IsoUnit>> XML_DURATION =
new XmlDurationRule();
//~ Konstruktoren -----------------------------------------------------
private XMLAdapter() {
super();
}
//~ Methoden ----------------------------------------------------------
private static DatatypeFactory getXMLFactory() {
try {
return DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException ex) {
throw new ChronoException("XML-conversion not available.", ex);
}
}
private static XMLGregorianCalendar toXML(
ChronoDisplay tsp,
int tz
) {
PlainDate date = tsp.get(PlainDate.COMPONENT);
int year = date.getYear();
int month = date.getMonth();
int dom = date.getDayOfMonth();
PlainTime time = tsp.get(PlainTime.COMPONENT);
int hour = time.getHour();
int minute = time.getMinute();
int second = tsp.get(PlainTime.SECOND_OF_MINUTE); // LS
int nano = time.getNanosecond();
DatatypeFactory factory = getXMLFactory();
if ((nano % MIO) == 0) {
int millis = nano / MIO;
return factory.newXMLGregorianCalendar(
year, month, dom, hour, minute, second, millis, tz);
} else {
BigInteger y = BigInteger.valueOf(year);
BigDecimal f =
BigDecimal.valueOf(nano).setScale(9, UNNECESSARY).divide(MRD_D, UNNECESSARY);
return factory.newXMLGregorianCalendar(
y, month, dom, hour, minute, second, f, tz);
}
}
//~ Innere Klassen ----------------------------------------------------
private static class XmlDateRule
extends XMLAdapter<XMLGregorianCalendar, PlainDate> {
//~ Methoden ------------------------------------------------------
@Override
public PlainDate translate(XMLGregorianCalendar source) {
BigInteger eon = source.getEon();
if (eon != null) {
BigInteger bi = eon.abs();
if (bi.compareTo(MRD_I) >= 0) {
throw new ArithmeticException(
"Year out of supported range: " + source);
}
}
int year = source.getYear();
int month = source.getMonth();
int dom = source.getDay();
if (
(year == DatatypeConstants.FIELD_UNDEFINED)
|| (month == DatatypeConstants.FIELD_UNDEFINED)
|| (dom == DatatypeConstants.FIELD_UNDEFINED)
) {
throw new ChronoException("Missing date component: " + source);
} else {
return PlainDate.of(year, month, dom);
}
}
@Override
public XMLGregorianCalendar from(PlainDate date) {
int year = date.getYear();
int month = date.getMonth();
int dom = date.getDayOfMonth();
DatatypeFactory factory = getXMLFactory();
return factory.newXMLGregorianCalendarDate(
year, month, dom, DatatypeConstants.FIELD_UNDEFINED);
}
@Override
public Class<XMLGregorianCalendar> getSourceType() {
return XMLGregorianCalendar.class;
}
}
private static class XmlTimeRule
extends XMLAdapter<XMLGregorianCalendar, PlainTime> {
//~ Methoden ------------------------------------------------------
@Override
public PlainTime translate(XMLGregorianCalendar source) {
int hour = source.getHour();
if (hour == DatatypeConstants.FIELD_UNDEFINED) {
throw new ChronoException("Missing hour component: " + source);
}
int minute = source.getMinute();
if (minute == DatatypeConstants.FIELD_UNDEFINED) {
minute = 0;
}
int second = source.getSecond();
if (second == DatatypeConstants.FIELD_UNDEFINED) {
second = 0;
}
int nano = 0;
BigDecimal fraction = source.getFractionalSecond();
if (fraction != null) {
nano = fraction.movePointRight(9).intValue();
}
return PlainTime.of(hour, minute, second, nano);
}
@Override
public XMLGregorianCalendar from(PlainTime time) {
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();
int nano = time.getNanosecond();
DatatypeFactory factory = getXMLFactory();
int noTZ = DatatypeConstants.FIELD_UNDEFINED;
if ((nano % MIO) == 0) {
int millis = nano / MIO;
return factory.newXMLGregorianCalendarTime(
hour, minute, second, millis, noTZ);
} else {
BigDecimal f =
BigDecimal.valueOf(nano).setScale(9, UNNECESSARY).divide(MRD_D, UNNECESSARY);
return factory.newXMLGregorianCalendarTime(
hour, minute, second, f, noTZ);
}
}
@Override
public Class<XMLGregorianCalendar> getSourceType() {
return XMLGregorianCalendar.class;
}
}
private static class XmlDateTimeRule
extends XMLAdapter<XMLGregorianCalendar, PlainTimestamp> {
//~ Methoden ------------------------------------------------------
@Override
public PlainTimestamp translate(XMLGregorianCalendar source) {
return this.translate(source, false);
}
PlainTimestamp translate(
XMLGregorianCalendar source,
boolean globalContext
) {
BigInteger eon = source.getEon();
if (eon != null) {
BigInteger bi = eon.abs();
if (bi.compareTo(MRD_I) >= 0) {
throw new ArithmeticException(
"Year out of supported range: " + source);
}
}
int year = source.getYear();
int month = source.getMonth();
int dom = source.getDay();
if (
(year == DatatypeConstants.FIELD_UNDEFINED)
|| (month == DatatypeConstants.FIELD_UNDEFINED)
|| (dom == DatatypeConstants.FIELD_UNDEFINED)
) {
throw new ChronoException("Missing date component: " + source);
}
int hour = source.getHour();
if (hour == DatatypeConstants.FIELD_UNDEFINED) {
throw new ChronoException("Missing hour component: " + source);
}
int minute = source.getMinute();
if (minute == DatatypeConstants.FIELD_UNDEFINED) {
minute = 0;
}
int second = source.getSecond();
if (second == DatatypeConstants.FIELD_UNDEFINED) {
second = 0;
} else if (globalContext && (second == 60)) {
second = 59;
}
int nano = 0;
BigDecimal fraction = source.getFractionalSecond();
if (fraction != null) {
nano = fraction.movePointRight(9).intValue();
}
PlainTimestamp tsp =
PlainTimestamp.of(year, month, dom, hour, minute, second);
if (nano != 0) {
tsp = tsp.with(PlainTime.NANO_OF_SECOND, nano);
}
return tsp;
}
@Override
public XMLGregorianCalendar from(PlainTimestamp tsp) {
return toXML(tsp, DatatypeConstants.FIELD_UNDEFINED);
}
@Override
public Class<XMLGregorianCalendar> getSourceType() {
return XMLGregorianCalendar.class;
}
}
private static class XmlDateTimeOffsetRule
extends XMLAdapter<XMLGregorianCalendar, ZonalDateTime> {
//~ Methoden ------------------------------------------------------
@Override
public ZonalDateTime translate(XMLGregorianCalendar source) {
PlainTimestamp tsp = XML_TIMESTAMP.translate(source, true);
int offsetMins = source.getTimezone();
if (offsetMins == DatatypeConstants.FIELD_UNDEFINED) {
throw new ChronoException("Missing timezone offset: " + source);
}
ZonalOffset offset = ZonalOffset.ofTotalSeconds(offsetMins * 60);
Moment moment = tsp.at(offset);
if (
(source.getSecond() == 60)
&& LeapSeconds.getInstance().isEnabled()
) {
Moment ls = moment.plus(1, SI.SECONDS);
if (ls.isLeapSecond()) {
return ls.inZonalView(offset);
} else {
throw new ChronoException(
"Leap second not registered: " + source);
}
} else {
return moment.inZonalView(offset);
}
}
@Override
public XMLGregorianCalendar from(ZonalDateTime zm) {
ZonalOffset offset = zm.getOffset();
int tz = offset.getIntegralAmount() / 60;
try {
return toXML(zm, tz);
} catch (IllegalArgumentException iae) {
if (zm.isLeapSecond()) {
// some XML-implementations are not conform to XML-Schema
ZonalDateTime pm =
zm.toMoment().minus(1, SI.SECONDS).inZonalView(offset);
return toXML(pm, tz);
} else {
throw iae;
}
}
}
@Override
public Class<XMLGregorianCalendar> getSourceType() {
return XMLGregorianCalendar.class;
}
}
private static class XmlDurationRule
extends XMLAdapter<javax.xml.datatype.Duration, Duration<IsoUnit>> {
//~ Methoden ------------------------------------------------------
@Override
public Duration<IsoUnit> translate(javax.xml.datatype.Duration source) {
if (source.getSign() == 0) {
return Duration.ofZero();
}
try {
return Duration.parsePeriod(source.toString());
} catch (ParseException ex) {
if (ex.getCause() instanceof NumberFormatException) {
ArithmeticException ae = new ArithmeticException();
ae.initCause(ex);
throw ae;
}
throw new ChronoException("Cannot translate: " + source, ex);
}
}
@Override
public javax.xml.datatype.Duration from(Duration<IsoUnit> duration) {
DatatypeFactory factory = getXMLFactory();
return factory.newDuration(duration.toStringXML());
}
@Override
public Class<javax.xml.datatype.Duration> getSourceType() {
return javax.xml.datatype.Duration.class;
}
}
}