//
// Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s).
// All rights reserved.
//
package openadk.library.impl;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import openadk.library.SIFFormatter;
/**
* Represents a SIFFormatter than can be used to format data to and from
* SIF 2.x datatypes
* @author Andrew Elmhorst
* @version 2.0
*
*/
public class SIF2xFormatter extends SIFFormatter {
// TODO: Keep an instance of XMLDataTypeFactory.newInstance().
// Use it to create new XMLGregorianCalendars... use toXMLFormat() to return the
// lexical representation
private DatatypeFactory fXmlDataTypeFactory = null;
/**
* SimpleDateFormat is not a thread-safe object, so we store one copy in each thread.
*/
private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>();
private static final ThreadLocal<SimpleDateFormat> timeFormat = new ThreadLocal<SimpleDateFormat>();
//private static final ThreadLocal<SimpleDateFormat> tzFormat = new ThreadLocal<SimpleDateFormat>();
public SIF2xFormatter()
{
fXmlDataTypeFactory = SIFFormatter.getDataTypeFactory();
}
/* (non-Javadoc)
* @see openadk.library.SIFFormatter#toDateString(java.util.Calendar)
*/
//FIXME Stephen Miller changed this for the same reason toTimeString was broken
public String toDateString( Calendar date ) {
if( date == null ){
return null;
}
// // TODO: This is still experimental
// XMLGregorianCalendar calendar = fXmlDataTypeFactory.newXMLGregorianCalendar( (GregorianCalendar) date );
//OLD METHOD
// Date rawDate = date.getTime();
// return getDateFormat().format( rawDate );
//NEW METHOD
//"yyyy-MM-dd"
DecimalFormat f2 = new DecimalFormat("00");
DecimalFormat f4 = new DecimalFormat("0000");
String year = f4.format( date.get(Calendar.YEAR));
String month = f2.format( date.get(Calendar.MONTH) + 1 );
String day = f2.format( date.get( Calendar.DAY_OF_MONTH) );
return year + "-" + month + "-" + day;
}
public String toDateTimeString( Calendar date ) {
if( date == null ){
return null;
}
StringBuilder buf = new StringBuilder();
buf.append( toDateString( date ) );
buf.append( "T" );
buf.append( toTimeString( date ) );
TimeZone zone = date.getTimeZone();
int offset = zone.getRawOffset() + zone.getDSTSavings();
int hourOffset = offset / 3600000;
int minuteOffset = (offset - (hourOffset * 3600000)) / 60000;
if ( offset == 0 ) {
buf.append("Z");
} else {
if( offset > 0 ){
buf.append( "+" );
} else if ( offset < 0) {
buf.append( "-" );
}
DecimalFormat f = new DecimalFormat("00");
buf.append( f.format( Math.abs( hourOffset ) ) );
buf.append( ':' );
buf.append( f.format( Math.abs( minuteOffset ) ) );
}
return buf.toString();
}
//FIXME I changed this to work properly across time zones. This should show up in the unit tests...
public String toTimeString( Calendar date ) {
// OLD METHOD
// if( date == null ){
// return null;
// }
// Date rawDate = date.getTime();
// return getTimeFormat().format( rawDate );
//NEW METHOD
if ( date == null ) {
return null;
}
DecimalFormat f = new DecimalFormat("00");
String hours = f.format(date.get(Calendar.HOUR_OF_DAY));
String minutes = f.format(date.get(Calendar.MINUTE));
String seconds = f.format(date.get(Calendar.SECOND));
return hours + ":" + minutes + ":" + seconds;
}
/* (non-Javadoc)
* @see openadk.library.SIFFormatter#toString(java.lang.Boolean)
*/
public String toString(Boolean boolValue) {
if( boolValue == null ){
return null;
}
return String.valueOf( boolValue );
}
/* (non-Javadoc)
* @see openadk.library.SIFFormatter#toDate(java.lang.String)
*/
public Calendar toDate(String value) {
if( value == null || value.length() == 0 ){
return null;
}
// TODO: This is still experimental
// XMLGregorianCalendar xgc = fXmlDataTypeFactory.newXMLGregorianCalendar( value );
// return xgc.toGregorianCalendar();
try {
Date parsedDate = getDateFormat().parse( value );
Calendar retValue = Calendar.getInstance();
retValue.clear();
retValue.setTime( parsedDate );
return retValue;
} catch( ParseException parseEx ){
throw new NumberFormatException( "Error parsing SIF 2.x formatted Date:'" + value + "'. " + parseEx.toString() );
}
}
/* (non-Javadoc)
* @see openadk.library.SIFFormatter#toBoolean(java.lang.String)
*/
public Boolean toBoolean(String value ) {
if( value == null ){
return null;
}
if( value.length() == 1 ){
return value.equals( "1" );
}
if( value.equalsIgnoreCase( "true" ) ){
return true;
} else if( value.equalsIgnoreCase( "false" ) ){
return false;
} else if( value.equalsIgnoreCase( "yes" ) ){
return true;
} else if( value.equalsIgnoreCase( "no" ) ){
return false;
}
throw new IllegalArgumentException( "Value '" + value + "' cannot be parsed into a Boolean" );
}
/* (non-Javadoc)
* @see openadk.library.SIFFormatter#toDuration(java.lang.String)
*/
@Override
public Duration toDuration(String xmlValue) {
return fXmlDataTypeFactory.newDuration( xmlValue );
}
/* (non-Javadoc)
* @see openadk.library.SIFFormatter#toString(javax.xml.datatype.Duration)
*/
@Override
public String toString(Duration d) {
if( d == null ){
return null;
}
return d.toString();
}
/**
* Returns true if the formatter supports writing the xsi:nil attribute.
* If the return value is false, SIFWriter will write an empty string
* @return true if the formatter supports writing the xsi:nil attribute
*/
public boolean supportsNamespaces(){
return true;
}
/**
* SimpleDateFormat is not thread-safe, so we use a ThreadLocal to store one copy
* on each thread. Call this method to get a copy of the SimpleDateFormat used
* to turn java.util.Date into a String formatted for SIF.
*
* @return A SimpleDateFormat that can turn a java.util.Date into an 8 character date in the SIF format.
*/
private synchronized SimpleDateFormat getDateFormat() {
SimpleDateFormat retval = dateFormat.get();
if (retval == null) {
retval = new SimpleDateFormat("yyyy-MM-dd");
retval.setLenient( false );
dateFormat.set(retval);
}
return retval;
}
/**
* Returns a Time format for SIF 2.x
* @return
*/
private synchronized SimpleDateFormat getTimeFormat() {
SimpleDateFormat retval = timeFormat.get();
if (retval == null) {
retval = new SimpleDateFormat("HH:mm:ss");
retval.setLenient( false );
timeFormat.set(retval);
}
return retval;
}
@Override
public String toString(BigDecimal decimalValue) {
if( decimalValue == null ){
return null;
}
return decimalValue.toPlainString();
}
@Override
public BigDecimal toDecimal(String decimalValue) {
if( decimalValue == null || decimalValue.length() == 0 ){
return null;
}
decimalValue = decimalValue.trim();
return new BigDecimal(decimalValue);
}
@Override
public Calendar toTime(String xmlValue) {
// TODO: This is still experimental
// XMLGregorianCalendar xgc = fXmlDataTypeFactory.newXMLGregorianCalendar( xmlValue );
// return xgc.toGregorianCalendar();
try{
String timePortion = xmlValue.substring( 0, 8 );
Date time = getTimeFormat().parse( timePortion );
Calendar returnValue = Calendar.getInstance();
returnValue.clear();
returnValue.setTime( time );
TimeZone timeZone = extractTimeZone( xmlValue, 8 );
if( timeZone != null ) {
int offset = TimeZone.getDefault().getRawOffset() - timeZone.getRawOffset();
returnValue.add( Calendar.MILLISECOND, offset );
}
return returnValue;
} catch( ParseException parseEx ){
throw new NumberFormatException( "Error parsing SIF 2.x formatted Date:'" + xmlValue + "'. " + parseEx.toString() );
} catch( StringIndexOutOfBoundsException parseEx ){
throw new NumberFormatException( "Error parsing SIF 2.x formatted Date:'" + xmlValue + "'. " + parseEx.toString() );
}
}
@Override
public Calendar toDateTime(String xmlValue) {
XMLGregorianCalendar xgc = fXmlDataTypeFactory.newXMLGregorianCalendar( xmlValue );
Calendar cal = xgc.toGregorianCalendar();
cal.get(Calendar.HOUR_OF_DAY);
cal.setTimeZone(TimeZone.getDefault());
cal.get(Calendar.HOUR_OF_DAY);
return cal;
}
private TimeZone extractTimeZone(String timeZoneString, int startPos) {
TimeZone timeZone = null;
if( timeZoneString.endsWith( "Z" ) ){
timeZone = TimeZone.getTimeZone( "GMT" );
} else if ( timeZoneString.length() == startPos + 6 ) {
String tz = "GMT" + timeZoneString.substring( startPos );
timeZone = TimeZone.getTimeZone( tz );
// int hoursOffset =Integer.parseInt( xmlValue.substring( 20, 22 ) );
// int minutesOffset = Integer.parseInt( xmlValue.substring( 23, 25 ) );
// int millisecondsOffset = hoursOffset * 60 * 60 * 1000 + minutesOffset * 60 * 1000;
// if( xmlValue.substring( 19, 20 ).equals( "-" ) )
// {
// millisecondsOffset = 0 - millisecondsOffset;
// }
//
// TimeZone test = TimeZone.getDefault();
// if( test.getRawOffset() == millisecondsOffset ){
// timeZone = test;
// } else {
// String[] availableIds = TimeZone.getAvailableIDs( millisecondsOffset );
// if( availableIds != null && availableIds.length > 0 ){
// timeZone = TimeZone.getTimeZone( availableIds[0] );
// }
// }
}
if( timeZone == null ) {
timeZone = TimeZone.getDefault();
}
return timeZone;
}
/**
* Converts a Java <c>int</c> value to a SIF int value
*
* @param intValue
* @return The int formatted as a string, using SIF formatting requirements
*/
public String toString(Integer intValue) {
if( intValue == null ){
return null;
}
return String.valueOf( intValue );
}
}