//
// Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s).
// All rights reserved.
//
package openadk.library.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import openadk.library.*;
import openadk.library.impl.surrogates.RenderSurrogate;
import openadk.library.impl.surrogates.XPathSurrogate;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
/**
* Provides metadata for a single SIF data object type or field (an attribute
* or child element). This information is used internally by the class framework
* to parse and render messages.
*
* @author Eric Petersen
* @version 1.0
*/
public class ElementDefImpl implements ElementDef
{
// Cached array of supported SIF versions for faster lookups
private static SIFVersion[] sSifVersions = ADK.getSupportedSIFVersions();
/**
* Flag indicating this is a field that should be rendered as an attribute
* of its parent rather than a child of its parent.
*/
public static final int FD_ATTRIBUTE = 0x01;;
/**
* Flag indicating this is a simple field element with no children
*/
public static final int FD_FIELD = 0x02;
/**
* Flag indicating this is a top-level element (SIF_Ack, StudentPersonal, etc.)
*/
public static final int FD_OBJECT = 0x04;
/**
* Flag indicating this element is deprecated in this version of SIF
*/
public static final int FD_DEPRECATED = 0x08;
/**
* Flag indicating this element is repeatable in this version of SIF
*/
public static final int FD_REPEATABLE = 0x10;
/**
* Flag indicating the content of this element should not be automatically
* escaped by the SIFWriter class.
*/
public static final int FD_DO_NOT_ENCODE = 0x20;
/**
* Flag indicating this element is a repeatable element container and that the
* container should be "collapsed" in this version of SIF.
* This causes the repeatable element container to not be written in this version of SIF
*/
public static final int FD_COLLAPSE = 0x40;
/**
* Flag indicating this element is the payload value of an XML Element
*/
public static final int FD_ELEMENT_VALUE = 0x80;
/**
* A special flag in SIF used to indicate that the specified element is deleted
*/
public static final ElementDefImpl DELETED_FLAG =
new ElementDefImpl(null,"Deleted",null,1,SIFDTD.common,(byte)0, SIFVersion.LATEST, SIFVersion.LATEST, SIFTypeConverters.BOOLEAN );
/**
* An array of VersionInfo objects that describe the element tag name or
* attribute name and sequence number for each version of SIF. The first
* element in the array represents SIF 1.0r1 (the first version supported
* by the ADK), and the last element in the array represents the last
* version of SIF supported by the ADK. Any array element with a null value
* is considered to be identical to the first non-null element preceding
* it.
*/
protected AbstractVersionInfo[] fInfo = new AbstractVersionInfo[ ADK.getSupportedSIFVersions().length ];
/**
* The version-independent name of this element (typically the same as
* the tag name for SIF 1.0r1)
*/
protected String fName;
/**
* Version-independent flags
*/
protected int fFlags;
/**
* The parent element
*/
protected ElementDefImpl fParent;
/**
* The local package name where this element is defined in the SDO class library
*/
protected String fPackage;
/**
* The children of this element
*/
private HashMap<String, ElementDef> fChildren;
/**
* The latest version this element or attribute appears in
*/
private final SIFVersion fLatestVersion;
/**
* The SIF data type converter to use for this element
*/
protected SIFTypeConverter fTypeConverter;
/**
* Constructs an ElementDef with flag<p>
*
* @param parent The parent of this element
* @param name The version-independent name of the element
* @param tag The element or attribute tag (if different from the name)
* @param sequence The zero-based ordering of this element within its parent
* or -1 if a top-level element
* @param localPackage The name of the package where the corresponding
* DataObject class is defined, excluding the
* <code>openadk.library</code> prefix
* @param earliestVersion The earliest version of SIF supported by this
* element. If the element is supported in any other version of SIF -
* or is deprecated in a later version - the SDOLibrary class must
* define it by calling <code>defineVersionInfo</code>
*/
public ElementDefImpl( ElementDef parent, String name, String tag, int sequence, String localPackage, SIFVersion earliestVersion, SIFVersion latestVersion )
{
this( parent, name, tag, sequence, localPackage, (byte)0, earliestVersion, latestVersion );
}
/**
* Constructs an ElementDef with flag<p>
*
* @param parent The parent of this element
* @param name The version-independent name of the element
* @param tag The element or attribute tag (if different from the name)
* @param sequence The zero-based ordering of this element within its parent
* or -1 if a top-level element
* @param localPackage The name of the package where the corresponding
* DataObject class is defined, excluding the
* <code>openadk.library</code> prefix
* @param flags One of the following: FD_ATTRIBUTE if this element should
* be rendered as an attribute of its parent rather than a child
* element; FD_FIELD if this element is a simple field with no child
* elements; or FD_OBJECT if this element is a SIF Data Object such
* as StudentPersonal or an infrastructure message such as SIF_Ack;
* FD_DEPRECATED if this element no longer applies to this version of
* SIF
* @param earliestVersion The earliest version of SIF supported by this
* element. If the element is supported in any other version of SIF -
* or is deprecated in a later version - the SDOLibrary class must
* define it by calling <code>defineVersionInfo</code>
*/
public ElementDefImpl( ElementDef parent, String name, String tag, int sequence, String localPackage, int flags, SIFVersion earliestVersion, SIFVersion latestVersion )
{
this( parent, name, tag, sequence, localPackage, flags, earliestVersion, latestVersion, (SIFTypeConverter)null );
}
public ElementDefImpl( ElementDef parent, String name, String tag, int sequence, String localPackage, int flags, SIFVersion earliestVersion, SIFVersion latestVersion, SIFTypeConverter typeConverter )
{
fName = name.intern();
if( ( flags & FD_ATTRIBUTE ) != 0 ){
// If this is an attribute, it is also a simple field
flags |= FD_FIELD;
}
fLatestVersion = latestVersion;
fFlags = flags;
fPackage = localPackage.intern();
fParent = (ElementDefImpl)parent;
fTypeConverter = typeConverter;
defineVersionInfo( earliestVersion, tag == null ? name : tag, sequence, flags );
if( fParent != null ){
fParent.addChild( this );
}
}
protected void addChild( ElementDef child ){
if( fChildren == null ) {
fChildren = new HashMap<String, ElementDef>();
}
fChildren.put( child.name(), child );
}
public void defineVersionInfo( SIFVersion version, String tag, int sequence, int flags )
{
AbstractVersionInfo vi = null;
for( int i = 0; i < sSifVersions.length; i++ ) {
if( version == sSifVersions[i] ) {
if( fInfo[i] == null ) {
fInfo[i] = vi = createVersionInfo( tag );
} else {
vi = fInfo[i];
}
break;
}
}
if( vi == null )
throw new IllegalArgumentException( "SIF " + version.toString() + " is not supported by the ADK" );
if( ( flags & FD_DEPRECATED ) != 0 )
vi.setFlag( AbstractVersionInfo.FLAG_DEPRECATED, true );
if( ( flags & FD_REPEATABLE ) != 0 )
vi.setFlag( AbstractVersionInfo.FLAG_REPEATABLE, true );
if( ( flags & FD_ATTRIBUTE ) != 0 )
vi.setFlag( AbstractVersionInfo.FLAG_ATTRIBUTE, true );
if( ( flags & FD_COLLAPSE ) != 0 ){
vi.setFlag( AbstractVersionInfo.FLAG_COLLAPSE, true );
}
vi.setSequence( sequence );
}
public String name()
{
return fName;
}
public String internalName()
{
return fName;
}
public String tag( SIFVersion version )
{
return info(version).getTag();
}
public int sequence( SIFVersion version )
{
return info(version).getSequence();
}
public String getSDOPath()
{
StringBuilder b = new StringBuilder( fName );
ElementDefImpl p = fParent;
while( p != null ) {
b.insert(0,p.fName+"_");
p = p.fParent;
}
return b.toString();
}
public String getSQPPath( SIFVersion version )
{
StringBuilder b = new StringBuilder( isAttribute( version ) ? "@" + tag(version) : tag(version) );
ElementDefImpl p = fParent;
while( p != null && !p.isObject() ) {
ElementVersionInfo evi = p.getVersionInfo( version );
if( !evi.isCollapsed() ){
b.insert( 0, '/' );
b.insert(0, evi.getTag() );
}
p = p.fParent;
}
return b.toString();
}
public ElementDef getParent()
{
return fParent;
}
public List<ElementDef> getChildren()
{
List<ElementDef>children = new ArrayList<ElementDef>();
if( fChildren != null ){
children.addAll( fChildren.values() );
// TODO: Add support for looking up the children of Common elements
// For example, LibraryPatronStatus_ElectronicIdList/ElectronicId has no children
// in it's metadata because it's been re-assigned
}
return children;
}
/**
* Gets the root metadata object
* @return The root metadata object
*/
public ElementDef getRoot()
{
ElementDefImpl d = this;
while( d.fParent != null ) {
d = d.fParent;
}
return d;
}
/**
* Returns the version-independent name of this element or attribute
* @see #getName
*/
public String toString() {
return fName;
}
public String getFQClassName() {
StringBuilder sbuf = new StringBuilder();
sbuf.append( "openadk.library." );
if( fPackage != null )
{
sbuf.append( fPackage );
sbuf.append( '.' );
}
sbuf.append( getClassName() );
return sbuf.toString();
}
public int getSequence( SIFVersion version ) {
return info(version).getSequence();
}
public boolean isAttribute( SIFVersion version ) {
return info(version).getFlag( AbstractVersionInfo.FLAG_ATTRIBUTE );
}
public boolean isField() {
return ( fFlags & FD_FIELD ) != 0;
}
public boolean isObject() {
return ( fFlags & FD_OBJECT ) != 0;
}
public boolean isElementValue() {
return ( fFlags & FD_ELEMENT_VALUE ) != 0;
}
public boolean isSupported( SIFVersion version )
{
SIFVersion earliestVersion = getEarliestVersion();
boolean retval = (earliestVersion == null || earliestVersion.compareTo( version ) < 1) &&
(fLatestVersion == null || fLatestVersion.compareTo( version ) > -1);
if (!retval) {
String earliest = "";
if (earliestVersion != null)
earliest = earliestVersion.toString();
String latest = "";
if (fLatestVersion != null)
latest = fLatestVersion.toString();
ADK.getLog().info(this.toString() + " Does NOT support version: " + version + " Earliest: " + earliest + " Latest : " + latest);
}
return retval;
}
public boolean isDeprecated( SIFVersion version ) {
return info(version).getFlag( AbstractVersionInfo.FLAG_DEPRECATED );
}
public boolean isRepeatable( SIFVersion version ) {
return info(version).getFlag( AbstractVersionInfo.FLAG_REPEATABLE );
}
public boolean isCollapsed( SIFVersion version ){
return info(version).getFlag( AbstractVersionInfo.FLAG_COLLAPSE );
}
public boolean isDoNotEncode() {
return ( fFlags & FD_DO_NOT_ENCODE ) != 0;
}
public String getPackage() {
return fPackage;
}
public SIFVersion getEarliestVersion() {
for( int i = 0; i < sSifVersions.length; i++ ) {
if( fInfo[i] != null )
return sSifVersions[i];
}
return null;
}
public SIFVersion getLatestVersion() {
return fLatestVersion;
}
/**
* Lookup the VersionInfo object for a specific version of SIF.
* @return The AbstractVersionInfo instance
* @throws RuntimeException if the ElementDef does not exist in the specified version of SIF
*/
protected AbstractVersionInfo info( SIFVersion v )
{
return getAbstractVersionInfo( v, true );
}
/**
* Lookup the VersionInfo object for a specific version of SIF.
* @param v The version of SIF to search
* @param throwIfNotExists True if a RuntimeException should be thrown if the version information does not exist.
* If False, NULL will be returned
* @return The AbstractVersionInfo instance or null
*/
protected AbstractVersionInfo getAbstractVersionInfo( SIFVersion v, boolean throwIfNotExists )
{
int last = -1;
// Search the list of SIFVersions that the ADK supports. The list
// is searched incrementally, starting with the oldest version. If
// a version is found that directly matches the requested SIF Version,
// return that entry. Otherwise, return the next previous entry from the list
for(int i= 0; i < sSifVersions.length; i++ )
{
int comparison = sSifVersions[i].compareTo( v );
if( comparison < 1 && fInfo[i] != null ){
last = i;
}
if( comparison > -1 )
{
// We have reached the SIFVersion in the list of supported versions that
// is greater than or equal to the requested version. Return the last AbstractVersionInfo
// from our array that we found
break;
}
}
if( last == -1 ){
if( throwIfNotExists ){
throw new RuntimeException( "Element or attribute \""+name()+"\" is not supported in SIF " + v.toString() );
} else {
return null;
}
}
return fInfo[last];
}
public SIFTypeConverter getTypeConverter() {
return fTypeConverter;
}
private AbstractVersionInfo createVersionInfo( String renderTag ){
if( renderTag.startsWith( "~") ){
// The tilde (~) symbolizes that this version needs a
// custom surrogate. The Surrogate expression syntax is:
// "~SurrogateName{constructor}renderAs"
int surrogateEnd = renderTag.lastIndexOf( '}' );
String surrogate = renderTag.substring( 0, surrogateEnd + 1 );
String renderAs = null;
if( surrogateEnd + 1 < renderTag.length() ){
renderAs = renderTag.substring( surrogateEnd + 1 );
}
if( renderAs == null ){
renderAs = name();
}
return new SurrogateVersionInfo( renderAs, surrogate, this );
} else {
return new TaggedVersionInfo( renderTag );
}
}
static abstract class AbstractVersionInfo implements ElementVersionInfo
{
/**
* Flag indicating this is a field that should be rendered as an attribute
* of its parent rather than a child of its parent.
*/
static final int FLAG_ATTRIBUTE = 0x01000000;
static final int FLAG_REPEATABLE = 0x02000000;
static final int FLAG_DEPRECATED = 0x04000000;
static final int FLAG_COLLAPSE = 0x08000000;
public int fFlag;
private String fTag;
protected AbstractVersionInfo( String tag )
{
if( tag !=null ){
fTag = tag.intern();
}
}
public void setSequence( int sequence ){
if( sequence >= 0 ){
fFlag = fFlag | sequence;
}
}
public int getSequence(){
return fFlag & 0x00FFFFFF;
}
public boolean getFlag( int flag ){
return (flag & fFlag) != 0;
}
public void setFlag( int flag, boolean value ){
if( value ){
fFlag |= flag;
} else {
fFlag &= ~flag;
}
}
public String getTag() {
return fTag;
}
public boolean isCollapsed() {
return getFlag( AbstractVersionInfo.FLAG_COLLAPSE );
}
public boolean isRepeatable() {
return getFlag( AbstractVersionInfo.FLAG_REPEATABLE );
}
public boolean isAttribute() {
return getFlag( AbstractVersionInfo.FLAG_ATTRIBUTE );
}
}
static class TaggedVersionInfo extends AbstractVersionInfo
{
public TaggedVersionInfo( String tag )
{
super( tag );
}
public RenderSurrogate getSurrogate() {
return null;
}
}
static class SurrogateVersionInfo extends AbstractVersionInfo
{
private RenderSurrogate fSurrogate;
private String fInitializer;
private ElementDef fDef;
public SurrogateVersionInfo( String renderAs, String surrogateString, ElementDef def )
{
super( renderAs );
fInitializer = surrogateString;
fDef = def;
}
@Override
public String getTag()
{
String tag = super.getTag();
if( tag == null ){
tag = getSurrogate().getPath();
}
return tag;
};
public synchronized RenderSurrogate getSurrogate() {
if( fSurrogate == null ){
String surrogateClassName = fInitializer;
String initializer = null;
int classInitializerStart = fInitializer.indexOf( "{" );
if( classInitializerStart > -1 ){
surrogateClassName = fInitializer.substring( 1, classInitializerStart );
initializer = fInitializer.substring( classInitializerStart + 1 );
if( initializer.equals( "}" ) ){
initializer = null;
} else {
initializer = initializer.substring( 0, initializer.length() -1 );
}
} else {
surrogateClassName = fInitializer.substring( 1 );
}
if( surrogateClassName.equals( "XPathSurrogate" ) ){
fSurrogate = new XPathSurrogate( fDef, initializer );
}
else {
try{
Class surrogateClass = Class.forName( "openadk.library.impl.surrogates." + surrogateClassName );
Constructor c = null;
if( initializer == null ){
c = surrogateClass.getConstructor( new Class[] { ElementDef.class } );
fSurrogate = ( RenderSurrogate )c.newInstance( fDef );
} else {
c = surrogateClass.getConstructor( new Class[] { ElementDef.class, String.class } );
fSurrogate = ( RenderSurrogate )c.newInstance( fDef, initializer );
}
} catch( Exception iex ){
throw new UnsupportedOperationException( "Surrogate " + fInitializer + " is not defined in this version of the ADK." + iex , iex );
}
}
// Now that the Surrogate has been loaded, the fInitializer
// data is no longer needed. Free the memory used by it
fInitializer = null;
}
return fSurrogate;
}
}
public ElementVersionInfo getVersionInfo(SIFVersion version) {
return getAbstractVersionInfo( version, false );
}
public String getClassName() {
return fName;
}
/* (non-Javadoc)
* @see openadk.library.ElementDef#hasSimpleContent()
*/
public boolean hasSimpleContent() {
return fTypeConverter != null;
}
/**
* Helper method that can be called from a Serializable class's writeObject
* method to serialize an ElementDef.<p>
*
* ElementDefImpl is not Serializable, so this method can be called by classes
* that need to serialize an ElementDef data member. The approach taken is to
* serialize only the SDO path to the ElementDef. During deserialization, that
* path is used to lookup the corresponding ElementDef in the ADK.DTD()
* dictionary. The process that is deserializing the object must have
* previously initialized the ADK.<p>
*
* @param element The ElementDef being serialized
* @param out The ObjectOutputStream to which the caller is serializing
*/
public static void writeObject( ElementDef element, ObjectOutputStream out ) throws IOException {
out.writeUTF(element.getSDOPath());
}
/**
* Helper method that can be called from a Serializable class's readObject
* method to deserialize an ElementDef.<p>
*
* ElementDefImpl is not Serializable, so this method can be called by classes
* that need to deserialize an ElementDef data member. The approach taken by
* the writeObject method is to serialize only the SDO path to the ElementDef.
* During deserialization, that path is used to lookup the corresponding
* ElementDef in the ADK.DTD() dictionary. The process that is deserializing
* the object must have previously initialized the ADK.<p>
*
* @param in The ObjectInputStream from which the caller is deserializing
* @return An ElementDef
*/
public static ElementDef readObject( ObjectInputStream in ) throws IOException {
if( !ADK.isInitialized() )
throw new RuntimeException("Cannot deserialize ElementDef: ADK is not initialized");
String path = in.readUTF();
if( path != null && path.length()>0 ) {
return ADK.DTD().lookupElementDef(path);
}
throw new RuntimeException( "Unable to deserialize ElementDef: " + path);
}
}