// // 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.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.List; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.Duration; import openadk.library.*; /** * Represents a SIFFormatter than can be used to format data to and from SIF 1.x * datatypes * * @author Andrew Elmhorst * @version 2.0 * */ public class SIF1xFormatter extends SIFFormatter { /** * SimpleDateFormat is not a thread-safe object, so we store one copy in * each thread. */ private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>(); /** * SimpleDateFormat is not a thread-safe object, so we store one copy in * each thread. */ private static final ThreadLocal<SimpleDateFormat> timeFormat = new ThreadLocal<SimpleDateFormat>(); /* * (non-Javadoc) * * @see openadk.library.SIFFormatter#toDateString(java.util.Calendar) */ public String toDateString(Calendar date) { if (date == null) { return ""; } return getDateFormat().format(date.getTime()); } /* * (non-Javadoc) * * @see openadk.library.SIFFormatter#toString(java.lang.Boolean) */ public String toString(Boolean boolValue) { if (boolValue == null) { return ""; } if( boolValue.booleanValue() ){ return "Yes"; } else { return "No"; } } @Override public String toTimeString(Calendar time) { if (time == null) { return null; } return getTimeFormat().format(time.getTime()); } @Override public Calendar toTime(String xmlValue) { if (xmlValue == null ) { return null; } xmlValue = xmlValue.trim(); if( xmlValue.length() == 0 ){ return null; } try { Date parsedDate = getTimeFormat().parse( xmlValue); Calendar retValue = Calendar.getInstance(); retValue.clear(); retValue.setTime(parsedDate); return retValue; } catch (ParseException parseEx) { throw new NumberFormatException( "Error parsing SIF 1.x formatted Date:'" + xmlValue + "'. " + parseEx.getMessage() ); } } /* * (non-Javadoc) * * @see openadk.library.SIFFormatter#toDate(java.lang.String) */ public Calendar toDate(String value) { if (value == null ) { return null; } value = value.trim(); if( value.length() == 0 ){ return null; } 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 1.x formatted Date:'" + value + "'. " + parseEx.getMessage() ); } } /* * (non-Javadoc) * * @see openadk.library.SIFFormatter#toBoolean(java.lang.String) */ public Boolean toBoolean(String value) { if (value == null ) { return null; } value = value.trim(); if( value.length() == 0 ){ return null; } if( value.equalsIgnoreCase( "yes" ) ){ return true; } else if( value.equalsIgnoreCase( "no" ) ){ return false; } else if( value.equalsIgnoreCase( "true" ) ){ return true; } else if( value.equalsIgnoreCase( "false" ) ){ return false; } throw new IllegalArgumentException( "Value '" + value + "' cannot be parsed into a Boolean" ); } /** * 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 false; } /** * 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 = (SimpleDateFormat) dateFormat.get(); if (retval == null) { retval = new SimpleDateFormat("yyyyMMdd"); retval.setLenient( false ); dateFormat.set(retval); } return retval; } /** * 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 getTimeFormat() { SimpleDateFormat retval = (SimpleDateFormat) timeFormat.get(); if (retval == null) { retval = new SimpleDateFormat( "HH:mm:ss" ); retval.setLenient( false ); timeFormat.set(retval); } return retval; } /** * Adds a SIFElement parsed from a specific version of SIF to the parent. * The formatter instance may use version-specific rules to ensure that the * hierarchy is properly maintained when the source of the content is from * this version of SIF * * @param contentParent * The element to add content to * @param content * The element to add * @param version * The version of SIF that the SIFElement is being constructed * from */ @SuppressWarnings("unchecked") public SIFElement addChild(SIFElement contentParent, SIFElement content, SIFVersion version) { contentParent.restoreImplementationDef( content ); return getContainer( contentParent, content.getElementDef(), version ).addChild( content ); } @Override public SimpleField setField(SIFElement contentParent, ElementDef fieldDef, SIFSimpleType data, SIFVersion version) { return getContainer( contentParent, fieldDef, version ).setField( fieldDef, data ); } private SIFElement getContainer( SIFElement contentParent, ElementDef childDef, SIFVersion version ) { ElementDef elementParentDef = childDef.getParent(); if( elementParentDef != null && elementParentDef != contentParent.getElementDef() ){ // The element does not appear to belong to this parent. Attempt to look // for a container element that might be missing in between the two // If the parent of this element were collapsed in a previous version // of SIF, check for or re-add the parent element and add this new // child instead. // // For example, a child could be an Email element from the // common package that's being added to StudentPersonal. In this // case, // we need to actually find or create an instance of the new // EmailList // container element and add the child to it, instead of to "this" String tag = elementParentDef.tag( ADK.getSIFVersion() ); ElementDef missingLink = ADK.DTD().lookupElementDef( contentParent.getElementDef(), tag ); if( missingLink != null && missingLink.isCollapsed( version ) ){ SIFElement container = contentParent.getChild( missingLink ); if (container == null) { try { container = SIFElement.create(contentParent, missingLink ); } catch (ADKSchemaException adkse) { throw new IllegalArgumentException(adkse.getMessage(), adkse); } } addChild(contentParent, container, version); return container; } } return contentParent; } /** * Gets the content from the SIFElement for the specified version of SIF. * Only elements that apply to the requested version of SIF will be * returned. Attributes are not returned. * * @param element * The element to retrieve content from * @param version * @return */ @SuppressWarnings("unchecked") public List<Element> getContent(SIFElement element, SIFVersion version) { List<Element> returnValue = new ArrayList<Element>(); List<SimpleField> fields = element.getFields(); for (SimpleField val : fields) { ElementDef def = val.getElementDef(); if ( def.isSupported( version ) && !def.isAttribute( version ) && def.isField() ){ returnValue.add(val); } } List<? extends SIFElement> children = element.getChildList(); for (SIFElement val : children) { ElementDef def = val.getElementDef(); if( def.isSupported( version ) ) { if (def.isCollapsed(version)) { List<? extends Element> subElements = getContent( val, version ); // FIXUP the ElementDef for this version of SIF. // for example, StudentPersonal/EmailList/Email needs it's // ElementDef set to "StudentPersonal_Email" for (Element e : subElements) { ElementDef subElementDef = e.getElementDef(); if (version.compareTo(subElementDef.getEarliestVersion()) >= 0) { String tag = subElementDef.tag( ADK.getSIFVersion() ); ElementDef restoredDef = ADK.DTD().lookupElementDef( element.getElementDef(), tag ); if (restoredDef != null) { e.setElementDef(restoredDef); } returnValue.add(e); } } } else { returnValue.add(val); } } } Collections.sort(returnValue, ElementSorter.getInstance(version)); return returnValue; } /** * Returns a string representation of the specified date. Since SIF 1.5 does * not support the datetime datatype, this field formats the date as a SIF * Date ("yyyyMMdd") * * @see openadk.library.SIFFormatter#toDateTimeString(java.util.Calendar) */ @Override public String toDateTimeString(Calendar date) { return toDateString(date); } @Override public String toString(BigDecimal decimalValue) { if( decimalValue == null ){ return null; } return decimalValue.toPlainString(); } @Override public BigDecimal toDecimal(String decimalValue) { if (decimalValue == null ) { return null; } decimalValue = decimalValue.trim(); if( decimalValue.length() == 0 ){ return null; } // SIF 1.5 allows some values to contain a percentage symbol. These can // be converted to decimal values after removing the percentage sign if( decimalValue.endsWith( "%" ) ){ decimalValue = decimalValue.substring( 0, decimalValue.length() - 1 ); } return new BigDecimal(decimalValue); } @Override public Calendar toDateTime(String xmlValue){ return toDate( xmlValue ); } @Override public Duration toDuration(String xmlValue) { if (xmlValue == null ) { return null; }xmlValue.trim(); if( xmlValue.length() == 0 ){ return null; } // TODO: This method probably needs to become more optimized // by storing the DatatypeFactory globally or re-using the one from // the SIF2xFormatter.... DatatypeFactory factory = null; try { factory = DatatypeFactory.newInstance(); } catch( DatatypeConfigurationException dce ){ throw new RuntimeException( dce.getMessage(), dce ); } Calendar time = toTime( xmlValue ); if( time != null ){ return factory.newDurationDayTime( true, 0, time.get( Calendar.HOUR ), time.get( Calendar.MINUTE ), time.get( Calendar.SECOND ) ); } return null; } @Override public String toString(Duration d) { // returns a string formatted as "HH:MM:SS" StringBuilder sb = new StringBuilder(); sb.append( d.getHours() ); sb.append( ':' ); sb.append( d.getMinutes() ); sb.append( ':' ); sb.append( d.getSeconds() ); return sb.toString(); } /** * 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 ""; } return String.valueOf( intValue ); } }