package org.nexml.model.impl;
import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import javax.xml.transform.Source;
import org.nexml.model.Annotation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class AnnotationImpl extends AnnotatableImpl implements Annotation {
private Object mValue;
private static String datatype = "datatype";
private static String href = "href";
private static String content = "content";
private static String rel = "rel";
private static String property = "property";
private static String anySimpleTypeLocal = "anySimpleType";
private static String LiteralMeta = NEX_PRE + ":LiteralMeta";
private static String ResourceMeta = NEX_PRE + ":ResourceMeta";
private static String XMLLiteral = RDF_PRE + ":XMLLiteral";
private static String anySimpleType = XSD_PRE + ":" + anySimpleTypeLocal;
private static QName anySimpleTypeQName = new QName(XS_URI,anySimpleTypeLocal,XSD_PRE);
@SuppressWarnings("serial")
private static Map<QName,Class<?>> classForXsdType = new HashMap<QName, Class<?>>() {{
put(new QName(XS_URI,"decimal",XSD_PRE), BigDecimal.class);
put(new QName(XS_URI,"integer",XSD_PRE), BigInteger.class);
put(new QName(XS_URI,"boolean",XSD_PRE), Boolean.class);
put(new QName(XS_URI,"byte",XSD_PRE), Byte.class);
put(new QName(XS_URI,"QName",XSD_PRE), QName.class);
put(new QName(XS_URI,"double",XSD_PRE), Double.class);
put(new QName(XS_URI,"float",XSD_PRE), Float.class);
put(new QName(XS_URI,"long",XSD_PRE), Long.class);
put(new QName(XS_URI,"short",XSD_PRE), Short.class);
put(new QName(XS_URI,"string",XSD_PRE), String.class);
put(new QName(XS_URI,"char",XSD_PRE), CharWrapper.class);
put(new QName(XS_URI,"dateTime",XSD_PRE), DateTimeWrapper.class);
put(new QName(XS_URI,"base64Binary",XSD_PRE), Base64BinaryWrapper.class);
put(new QName(XS_URI,"duration",XSD_PRE), DurationWrapper.class);
}};
@SuppressWarnings("serial")
private static Map<Class<?>,QName> xsdTypeForClass = new HashMap<Class<?>,QName>() {{
for ( QName xsdType : classForXsdType.keySet() ) {
put(classForXsdType.get(xsdType), xsdType);
}
put(Integer.class,new QName(XS_URI,"integer",XSD_PRE));
put(Date.class, new QName(XS_URI,"dateTime",XSD_PRE));
put(Calendar.class, new QName(XS_URI,"dateTime",XSD_PRE));
put(UUID.class, new QName(XS_URI,"string",XSD_PRE));
put(java.awt.Image.class, new QName(XS_URI,"base64Binary",XSD_PRE));
put(Duration.class, new QName(XS_URI,"duration",XSD_PRE));
put(java.lang.Character.class, new QName(XS_URI,"char",XSD_PRE));
put(Source.class, new QName(XS_URI,"base64Binary",XSD_PRE));
}};
/**
* Class version of {@code getTagName()}.
*
* @return the tag name.
*/
static String getTagNameClass() {
return "meta";
}
/*
* (non-Javadoc)
* @see org.nexml.model.impl.NexmlWritableImpl#getTagName()
*/
@Override
String getTagName() {
return getTagNameClass();
}
/**
* Protected constructors are intended for recursive parsing, i.e.
* starting from the root element (which maps onto DocumentImpl) we
* traverse the element tree such that for every child element that maps
* onto an Impl class the containing class calls that child's protected
* constructor, passes in the element of the child. From there the
* child takes over, populates itself and calls the protected
* constructors of its children. These should probably be protected
* because there is all sorts of opportunity for outsiders to call
* these in the wrong context, passing in the wrong elements etc.
* @param document the containing DOM document object. Every Impl
* class needs a reference to this so that it can create DOM element
* objects
* @param element the equivalent NeXML element (e.g. for OTUsImpl, it's
* the <otus/> element)
* @author rvosa
*/
protected AnnotationImpl(Document document, Element element) {
super(document, element);
if ( element.hasAttribute(content) ) {
String datatypeString = element.getAttribute(datatype);
String[] curie = datatypeString.split(":");
QName datatype = new QName(XS_URI,curie[1],curie[0]);
String contentValue = element.getAttribute(content);
Class<?> theClass = getClassForXsdType(datatype);
try {
Constructor<?>[] constructors = theClass.getConstructors();
for ( int i = 0; i < constructors.length; i++ ) {
Class<?>[] params = constructors[i].getParameterTypes();
if ( params.length == 1 && params[0].equals(String.class) ) {
mValue = constructors[i].newInstance(contentValue);
}
else if ( params.length == 2 && params[0].equals(this.getClass()) && params[1].equals(String.class) ) {
mValue = constructors[i].newInstance(this,contentValue);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
else if ( element.hasAttribute(href) ) {
mValue = URI.create(element.getAttribute(href));
}
// there is no content or href
else {
// maybe recursive meta?
List<Element> metas = getChildrenByTagName(getElement(),getTagName());
if ( metas.size() > 0 ) {
Set<Annotation> nestedAnnotations = new HashSet<Annotation>();
for ( Element meta : metas ) {
Annotation anno = new AnnotationImpl(getDocument(),meta);
nestedAnnotations.add(anno);
}
mValue = nestedAnnotations;
}
else {
NodeList literalXmlNodes = element.getChildNodes();
mValue = literalXmlNodes;
}
}
}
/**
* Protected constructors that take a DOM document object but not
* an element object are used for generating new element nodes in
* a NeXML document. On calling such constructors, a new element
* is created, which can be retrieved using getElement(). After this
* step, the Impl class that called this constructor would still
* need to attach the element in the proper location (typically
* as a child element of the class that called the constructor).
* @param document a DOM document object
* @author rvosa
*/
protected AnnotationImpl(Document document) {
super(document);
}
protected AnnotationImpl() {}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#getProperty()
*/
public String getProperty() {
return getElement().getAttribute(property);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#getValue()
*/
public Object getValue() {
return mValue;
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setProperty(java.lang.String)
*/
public void setProperty(String propertyValue) {
getElement().setAttribute(property, propertyValue);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#getRel()
*/
public String getRel() {
return getElement().getAttribute(rel);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setRel(java.lang.String)
*/
public void setRel(String relValue) {
getElement().setAttribute(rel,relValue);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.lang.Object)
*/
public void setValue(Set<Annotation> value) {
mValue = value;
getElement().setAttribute(XSI_TYPE,ResourceMeta);
for ( Annotation annotation : value ) {
getElement().appendChild(((AnnotationImpl)annotation).getElement());
}
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(org.nexml.model.Annotation)
*/
public void setValue(Annotation value) {
mValue = value;
getElement().setAttribute(XSI_TYPE,ResourceMeta);
getElement().appendChild(((AnnotationImpl)value).getElement());
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.lang.Object)
*/
public void setValue(Object value) {
mValue = value;
getElement().setAttribute(datatype, anySimpleType);
getElement().setAttribute(XSI_TYPE,LiteralMeta);
if ( null != value ) {
getElement().setAttribute(content,value.toString());
}
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(org.w3c.dom.NodeList)
*/
public void setValue(NodeList value) {
mValue = value;
getElement().setAttribute(datatype, XMLLiteral);
getElement().setAttribute(XSI_TYPE,LiteralMeta);
for ( int i = 0; i < ((NodeList)value).getLength(); i++ ) {
Node node = ((NodeList)value).item(i);
if ( node.getOwnerDocument() != getDocument() ) {
node = getDocument().importNode(node,true);
}
getElement().appendChild(node);
}
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(org.w3c.dom.Node)
*/
public void setValue(Node value) {
mValue = value;
getElement().setAttribute(datatype, XMLLiteral);
getElement().setAttribute(XSI_TYPE,LiteralMeta);
if ( value.getOwnerDocument() != getDocument() ) {
value = getDocument().importNode(value,true);
}
getElement().appendChild(value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(org.w3c.dom.Element)
*/
public void setValue(Element value) {
mValue = value;
getElement().setAttribute(datatype, XMLLiteral);
getElement().setAttribute(XSI_TYPE,LiteralMeta);
if ( value.getOwnerDocument() != getDocument() ) {
value = (Element) getDocument().importNode(value,true);
}
getElement().appendChild(value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.net.URI)
*/
public void setValue(URI value) {
mValue = value;
String valueString = value.toString();
if ( null != getBaseURI() ) {
String baseURIString = getBaseURI().toString();
int baseURILength = baseURIString.length();
// truncate stringified value to omit base uri, if matches
if ( baseURILength > 0 ) {
if ( valueString.startsWith(baseURIString) ) {
getElement().setAttribute(href, valueString.substring(baseURILength));
}
else {
getElement().setAttribute(href, valueString);
}
}
else {
getElement().setAttribute(href, valueString);
}
}
else {
getElement().setAttribute(href, valueString);
}
getElement().setAttribute(XSI_TYPE,ResourceMeta);
}
/**
* Helper method to fill out the boiler plate for atomic literal meta objects
* @param datatype schema datatype, i.e. a CURIE such as xsd:string
* @param value a marshalled version of the object
*/
private void setValueAttributes(QName datatypeQName,Object value) {
mValue = value;
StringBuffer sb = new StringBuffer();
sb.append(datatypeQName.getPrefix()).append(':').append(datatypeQName.getLocalPart());
getElement().setAttribute(datatype,sb.toString());
getElement().setAttribute(XSI_TYPE,LiteralMeta);
//getElement().appendChild(getDocument().createTextNode(value.toString()));
// XXX changing this to conform to:
// * http://www.xml.com/pub/a/2007/02/14/introducing-rdfa.html
// * http://dublincore.org/documents/dcq-html/
// * http://en.wikipedia.org/wiki/RDFa#XHTML.2BRDFa_1.0_example
getElement().setAttribute(content,value.toString());
}
/**
* these setters are the default JAXB mappings, see
* http://java.sun.com/javaee/5/docs/tutorial/doc/bnazq.html#bnazt
* */
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.lang.Byte[])
*/
public void setValue(Byte[] value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.math.BigDecimal)
*/
public void setValue(BigDecimal value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.math.BigInteger)
*/
public void setValue(BigInteger value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.lang.Boolean)
*/
public void setValue(Boolean value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.lang.Byte)
*/
public void setValue(Byte value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.util.Calendar)
*/
public void setValue(Calendar value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.util.Date)
*/
public void setValue(Date value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.lang.Double)
*/
public void setValue(Double value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.lang.Float)
*/
public void setValue(Float value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.lang.Integer)
*/
public void setValue(Integer value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.lang.Long)
*/
public void setValue(Long value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.lang.Short)
*/
public void setValue(Short value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.util.UUID)
*/
public void setValue(UUID value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.lang.String)
*/
public void setValue(String value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.awt.Image)
*/
public void setValue(java.awt.Image value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(javax.xml.datatype.Duration)
*/
public void setValue(Duration value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(javax.xml.namespace.QName)
*/
public void setValue(QName value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(java.lang.Character)
*/
public void setValue(java.lang.Character value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(javax.xml.transform.Source)
*/
public void setValue(Source value) {
setValueAttributes(getXsdTypeForClass(value.getClass()),value);
}
/*
* (non-Javadoc)
* @see org.nexml.model.Annotation#setValue(javax.xml.datatype.XMLGregorianCalendar)
*/
public void setValue(XMLGregorianCalendar value) {
mValue = value;
getElement().setAttribute(datatype,anySimpleType);
getElement().setAttribute(XSI_TYPE,LiteralMeta);
//getElement().appendChild(getDocument().createTextNode(value.toString()));
// XXX changing this to conform to:
// * http://www.xml.com/pub/a/2007/02/14/introducing-rdfa.html
// * http://dublincore.org/documents/dcq-html/
// * http://en.wikipedia.org/wiki/RDFa#XHTML.2BRDFa_1.0_example
getElement().setAttribute(content,value.toXMLFormat());
}
/**
* Test to see if provided class has a mapping to an xsd type
*/
@Override
public boolean isValueMapped(Class<?> valueClass) {
return xsdTypeForClass.containsKey(valueClass);
}
/**
* Returns the value of the "datatype" attribute for the focal
* annotation as a QName
*/
public QName getXsdType() {
return getXsdTypeForClass(mValue.getClass());
}
/**
* Gets the namespace URI for the predicate (either the "property"
* or "rel" attribute in the case of annotations with URLs or nested
* annotations). Traverses up the element tree if the URI is declared
* on an ancestral element.
*/
public URI getPredicateNamespace() {
String property = getProperty();
if ( isEmpty(property) ) {
property = getRel();
}
String[] parts = property.split(":");
String prefix = parts[0];
if ( ! isEmpty(prefix) ) {
Element elt = getElement();
String xmlnsAttr = XMLNS_PRE + ":" + prefix;
String xmlns = elt.getAttribute(xmlnsAttr);
while ( isEmpty(xmlns) ) {
if ( elt.getParentNode() instanceof Element ) {
elt = (Element)elt.getParentNode();
xmlns = elt.getAttribute(xmlnsAttr);
}
else {
break;
}
}
if ( ! isEmpty(xmlns) ) {
return URI.create(xmlns);
}
}
return null;
}
/**
* Test to see if provided string is either null or ""
* @param str
* @return
*/
private static boolean isEmpty(String str) {
return null == str || "".equals(str);
}
/**
* Given a class name, returns the QName that is to be used
* as the value of the "datatype" attribute when serialized.
* If no mapping is found, this method first checks if there
* is a mapping for a superclass; for example, if we pass in
* DurationImpl, we get the mapping back for Duration (i.e.
* xsd:duration). If no mapping exists at all, e.g. for a
* SimpleObject, this method returns xsd:anySimpleType.
* @param theClass
* @return
*/
private static QName getXsdTypeForClass(Class<?> theClass) {
if ( xsdTypeForClass.containsKey(theClass) ) {
return xsdTypeForClass.get(theClass);
}
else {
QName result = anySimpleTypeQName;
for ( Class<?> candidateClass : xsdTypeForClass.keySet() ) {
if ( candidateClass.isAssignableFrom(theClass) ) {
result = xsdTypeForClass.get(candidateClass);
}
}
return result;
}
}
/**
* Given a QName, presumably parsed as the value of the "datatype"
* attribute of an annotation, returns the corresponding class. If
* No such class exists, the SimpleObject class is returned, which
* is a simple wrapper around a string, but which ensures that this
* string is serialized as an xsd:anySimpleType, not an xsd:String
* @param theType
* @return
*/
private static Class<?> getClassForXsdType(QName theType) {
if ( classForXsdType.containsKey(theType) ) {
return classForXsdType.get(theType);
}
else {
return SimpleObject.class;
}
}
/* INNER CLASSES
* These classes are used to deal with situations where we read an
* annotation for which no equivalent java class exists that can be
* instantiated with a simple String argument. This is the case for
* the following data types:
* - xsd:anySimpleType
* - xsd:char
* - xsd:dateTime
* - xsd:base64Binary
* - xsd:duration
*/
/**
* A wrapper for xsd:anySimpleType holding an opaque string
* @author rvosa
*/
class SimpleObject {
private String mValue;
public SimpleObject(String value) {
mValue = value;
}
public String toString() {
return mValue;
}
}
/**
* Wrapper for xsd:char values
* @author rvosa
*/
class CharWrapper {
private java.lang.Character mValue;
public CharWrapper(String value) {
mValue = new java.lang.Character(value.charAt(0));
}
public String toString() {
return mValue.toString();
}
}
/**
* Wrapper for xsd:dateTime values
* @author rvosa
*/
class DateTimeWrapper {
private Date mValue;
public DateTimeWrapper(String value) {
try {
mValue = DateFormat.getDateTimeInstance().parse(value);
} catch (ParseException e) {
e.printStackTrace();
}
}
public String toString() {
return mValue.toString();
}
}
/**
* Wrapper for xsd:base64Binary values
* @author rvosa
*/
class Base64BinaryWrapper {
private Byte[] mValue;
public Base64BinaryWrapper(String value) {
byte[] bytes = value.getBytes();
mValue = new Byte[bytes.length];
for ( int i = 0; i < bytes.length; i++ ) {
((Byte[])mValue)[i] = bytes[i];
}
}
public String toString() {
StringBuffer sb = new StringBuffer();
for ( int i = 0; i < mValue.length; i++ ) {
sb.append(mValue[i]);
}
return sb.toString();
}
}
/**
* Wrapper for xsd:duration values
* @author rvosa
*/
class DurationWrapper {
private Duration mValue;
public DurationWrapper(String value) {
DatatypeFactory dtf = null;
try {
dtf = DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException e) {
e.printStackTrace();
}
mValue = dtf.newDuration(value);
}
public String toString() {
return mValue.toString();
}
}
}