/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This 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.
*
* This software 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 this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.xb.binding.metadata.unmarshalling.impl;
import org.jboss.xb.binding.metadata.unmarshalling.DocumentBindingFactory;
import org.jboss.xb.binding.metadata.unmarshalling.NamespaceBinding;
import org.jboss.xb.binding.metadata.unmarshalling.DocumentBinding;
import org.jboss.xb.binding.metadata.unmarshalling.TopElementBinding;
import org.jboss.xb.binding.metadata.unmarshalling.ElementBinding;
import org.jboss.xb.binding.metadata.unmarshalling.BasicElementBinding;
import org.jboss.xb.binding.metadata.unmarshalling.AttributeBinding;
import org.jboss.xb.binding.metadata.unmarshalling.DocumentBindingStack;
import org.jboss.xb.binding.metadata.unmarshalling.DocumentBinder;
import org.jboss.xb.binding.metadata.unmarshalling.XmlValueBinding;
import org.jboss.xb.binding.metadata.unmarshalling.XmlValueContainer;
import org.jboss.xb.binding.JBossXBRuntimeException;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Constructor;
/**
* @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
* @version <tt>$Revision: 1455 $</tt>
*/
public class DocumentBindingFactoryImpl
extends DocumentBindingFactory
{
public DocumentBindingStack newDocumentBindingStack()
{
return new DocumentBindingStackImpl();
}
public NamespaceBinding bindNamespace(DocumentBinding doc, String namespaceUri, String javaPackage)
{
DocumentBindingStackImpl docStack = (DocumentBindingStackImpl)doc;
return docStack.bindNamespace(new DefaultNamespaceBinding(docStack, namespaceUri, javaPackage));
}
public TopElementBinding bindTopElement(NamespaceBinding ns, String elementName, Class javaClass)
{
NamespaceBindingStack nsStack = (NamespaceBindingStack)ns;
return nsStack.bindTopElement(new DefaultTopElementBinding(nsStack, elementName, javaClass));
}
public ElementBinding bindElement(BasicElementBinding parent,
String namespaceUri,
String elementName,
String fieldName,
Class javaType)
{
BasicElementBindingStack basicStack = (BasicElementBindingStack)parent;
return basicStack.bindChild(
new DefaultElementBinding(parent, new QName(namespaceUri, elementName), fieldName, javaType)
);
}
public XmlValueBinding bindValue(BasicElementBinding element, String fieldName, Class javaType)
{
ElementBindingStack basicStack = (ElementBindingStack)element;
return basicStack.bindValue(new DefaultXmlValueBinding(element, fieldName, javaType));
}
public XmlValueBinding bindValue(XmlValueContainer container, String fieldName, Class javaType)
{
XmlValueContainerStack stack = (XmlValueContainerStack)container;
return stack.bindValue(new DefaultXmlValueBinding(container, fieldName, javaType));
}
public AttributeBinding bindAttribute(BasicElementBinding parent,
String namespaceUri,
String attributeName,
String fieldName,
Class javaType)
{
BasicElementBindingStack stack = (BasicElementBindingStack)parent;
AttributeBinding attr = new AttributeBindingImpl(new QName(namespaceUri, attributeName),
javaType,
parent.getJavaType(),
fieldName
);
stack.bindAttribute(attr);
return attr;
}
public DocumentBinding newDocumentBinding()
{
return new DocumentBindingStackImpl();
}
//
// Inner
//
// Abstract classes for this stack implementation
public static abstract class AbstractDocumentBinding
implements DocumentBinding
{
protected final DocumentBinding doc;
protected AbstractDocumentBinding(DocumentBinding doc)
{
if(doc == null)
{
// todo
doc = DocumentBindingFactory.newInstance().newDocumentBindingStack();
((DocumentBindingStackImpl)doc).push(this);
}
this.doc = doc;
}
public NamespaceBinding getNamespace(String namespaceUri)
{
return doc.getNamespace(namespaceUri);
}
protected abstract NamespaceBinding getNamespaceLocal(String namespaceUri);
}
public static abstract class AbstractNamespaceBinding
implements NamespaceBinding
{
protected final String namespaceUri;
private final DocumentBinding doc;
protected AbstractNamespaceBinding(DocumentBinding doc, String namespaceUri)
{
this.namespaceUri = namespaceUri;
this.doc = doc;
}
public DocumentBinding getDocument()
{
return doc;
}
public String getNamespaceUri()
{
return namespaceUri;
}
public String getJavaPackage()
{
return doc.getNamespace(namespaceUri).getJavaPackage();
}
public TopElementBinding getTopElement(String elementName)
{
return doc.getNamespace(namespaceUri).getTopElement(elementName);
}
protected abstract String getJavaPackageLocal();
protected abstract TopElementBinding getTopElementLocal(String elementName);
}
public static abstract class AbstractXmlValueContainer
implements XmlValueContainer
{
protected final QName name;
public AbstractXmlValueContainer(QName name)
{
this.name = name;
}
public QName getName()
{
return name;
}
public XmlValueBinding getValue()
{
return getValueStackReference().getValue();
}
public Class getJavaType()
{
return getValueStackReference().getJavaType();
}
protected abstract XmlValueContainer getValueStackReference();
protected abstract XmlValueBinding getValueLocal();
protected abstract Class getJavaTypeLocal();
}
public static abstract class AbstractBasicElementBinding
extends AbstractXmlValueContainer
implements BasicElementBinding
{
protected AbstractBasicElementBinding(QName elementName)
{
super(elementName);
}
public DocumentBinding getDocument()
{
return getStackReference().getDocument();
}
public ElementBinding getElement(QName elementName)
{
return getStackReference().getElement(elementName);
}
public AttributeBinding getAttribute(QName attributeName)
{
return getStackReference().getAttribute(attributeName);
}
public XmlValueBinding getValue()
{
return getStackReference().getValue();
}
protected XmlValueContainer getValueStackReference()
{
return getStackReference();
}
protected abstract Class getJavaTypeLocal();
protected abstract ElementBinding getElementLocal(QName elementName);
protected abstract AttributeBinding getAttributeLocal(QName attributeName);
protected abstract XmlValueBinding getValueLocal();
protected abstract BasicElementBinding getStackReference();
}
public static abstract class AbstractTopElementBinding
extends AbstractBasicElementBinding
implements TopElementBinding
{
protected final NamespaceBinding ns;
protected AbstractTopElementBinding(NamespaceBinding ns, String elementName)
{
super(new QName(ns.getNamespaceUri(), elementName));
this.ns = ns;
}
protected BasicElementBinding getStackReference()
{
return ns.getTopElement(name.getLocalPart());
}
}
public static abstract class AbstractElementBinding
extends AbstractBasicElementBinding
implements ElementBinding
{
protected final BasicElementBinding parent;
protected AbstractElementBinding(BasicElementBinding parent, QName elementName)
{
super(elementName);
this.parent = parent;
}
public Field getField()
{
return ((ElementBinding)getStackReference()).getField();
}
public Method getGetter()
{
return ((ElementBinding)getStackReference()).getGetter();
}
public Method getSetter()
{
return ((ElementBinding)getStackReference()).getSetter();
}
public Class getFieldType()
{
return ((ElementBinding)getStackReference()).getFieldType();
}
protected BasicElementBinding getStackReference()
{
return parent.getElement(name);
}
protected abstract Field getFieldLocal();
protected abstract Method getGetterLocal();
protected abstract Method getSetterLocal();
protected abstract Class getFieldTypeLocal();
}
public static abstract class AbstractXmlValueBinding
extends AbstractXmlValueContainer
implements XmlValueBinding
{
private final XmlValueContainer container;
protected AbstractXmlValueBinding(XmlValueContainer container)
{
super(container.getName());
this.container = container;
}
public Field getField()
{
return container.getValue().getField();
}
public Method getGetter()
{
return container.getValue().getGetter();
}
public Method getSetter()
{
return container.getValue().getSetter();
}
public Class getFieldType()
{
return container.getValue().getFieldType();
}
public XmlValueBinding getValue()
{
return container.getValue().getValue();
}
protected XmlValueContainer getValueStackReference()
{
return container.getValue();
}
protected abstract Field getFieldLocal();
protected abstract Method getGetterLocal();
protected abstract Method getSetterLocal();
protected abstract Class getFieldTypeLocal();
}
// Default impl used internally
private static class DefaultNamespaceBinding
extends AbstractNamespaceBinding
{
private final String javaPackage;
public DefaultNamespaceBinding(DocumentBinding doc, String namespaceUri, String javaPackage)
{
super(doc, namespaceUri);
this.javaPackage = javaPackage;
}
protected String getJavaPackageLocal()
{
return javaPackage;
}
protected TopElementBinding getTopElementLocal(String elementName)
{
return null;
}
}
private static class DefaultTopElementBinding
extends AbstractTopElementBinding
{
private final Class javaType;
DefaultTopElementBinding(NamespaceBinding ns, String elementName, Class javaType)
{
super(ns, elementName);
this.javaType = javaType;
}
protected Class getJavaTypeLocal()
{
return javaType;
}
protected ElementBinding getElementLocal(QName elementName)
{
return null;
}
protected AttributeBinding getAttributeLocal(QName attributeName)
{
return null;
}
protected XmlValueBinding getValueLocal()
{
return null;
}
}
private static class DefaultElementBinding
extends AbstractElementBinding
{
private final Field field;
private final Method getter;
private final Method setter;
private final Class fieldType;
private final Class javaType;
public DefaultElementBinding(BasicElementBinding parent, QName elementName, String fieldName, Class javaType)
{
super(parent, elementName);
Class parentType = parent.getJavaType();
if(Collection.class.isAssignableFrom(parentType))
{
field = null;
getter = null;
setter = null;
fieldType = null;
}
else
{
Field tmpField = null;
Method tmpGetter = null;
Method tmpSetter = null;
Class tmpFieldType;
try
{
tmpField = parentType.getField(fieldName);
tmpFieldType = tmpField.getType();
}
catch(NoSuchFieldException e)
{
String baseMethodName = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
try
{
tmpGetter = parentType.getMethod("get" + baseMethodName, null);
tmpFieldType = tmpGetter.getReturnType();
try
{
tmpSetter = parentType.getMethod("set" + baseMethodName, new Class[]{tmpGetter.getReturnType()});
}
catch(NoSuchMethodException nosetter)
{
// this one is immutable
}
}
catch(NoSuchMethodException e1)
{
throw new JBossXBRuntimeException("Failed to bind " +
elementName +
" to field " +
fieldName +
" in " +
parentType +
": neither field nor getter/setter were found."
);
}
}
field = tmpField;
getter = tmpGetter;
setter = tmpSetter;
fieldType = tmpFieldType;
}
if(fieldType == null)
{
this.javaType = javaType == null ? String.class : javaType;
}
else
{
if(javaType == null)
{
this.javaType = fieldType;
}
else if(Collection.class == fieldType ||
Collection.class.isAssignableFrom(fieldType) ||
fieldType.isAssignableFrom(javaType))
{
this.javaType = javaType;
}
else
{
throw new JBossXBRuntimeException("Failed to bind " +
elementName +
" to field " +
fieldName +
" in " +
parentType +
": field type " + fieldType + " is not assignable from the specified Java type " + javaType
);
}
if(this.javaType.isInterface() || Modifier.isAbstract(this.javaType.getModifiers()))
{
throw new JBossXBRuntimeException("Failed to bind " +
elementName +
" to field " +
fieldName +
" in " +
parentType +
": Java type is abstract class or interface."
);
}
}
}
protected Field getFieldLocal()
{
return field;
}
protected Method getGetterLocal()
{
return getter;
}
protected Method getSetterLocal()
{
return setter;
}
protected Class getFieldTypeLocal()
{
return fieldType;
}
protected Class getJavaTypeLocal()
{
return javaType;
}
protected ElementBinding getElementLocal(QName elementName)
{
return null;
}
protected AttributeBinding getAttributeLocal(QName attributeName)
{
return null;
}
protected XmlValueBinding getValueLocal()
{
return null;
}
}
private static class DefaultXmlValueBinding
extends AbstractXmlValueBinding
{
private final Field field;
private final Method getter;
private final Method setter;
private final Class fieldType;
private final Class javaType;
public DefaultXmlValueBinding(XmlValueContainer container, String fieldName, Class javaType)
{
super(container);
Class parentType = container.getJavaType();
if(Collection.class.isAssignableFrom(parentType))
{
field = null;
getter = null;
setter = null;
fieldType = null;
}
else
{
Field tmpField = null;
Method tmpGetter = null;
Method tmpSetter = null;
Class tmpFieldType = null;
try
{
tmpField = parentType.getField(fieldName);
tmpFieldType = tmpField.getType();
}
catch(NoSuchFieldException e)
{
String baseMethodName = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
try
{
tmpGetter = parentType.getMethod("get" + baseMethodName, null);
tmpFieldType = tmpGetter.getReturnType();
try
{
tmpSetter = parentType.getMethod("set" + baseMethodName, new Class[]{tmpGetter.getReturnType()});
}
catch(NoSuchMethodException nosetter)
{
// this one is immutable
}
}
catch(NoSuchMethodException e1)
{
throw new JBossXBRuntimeException("Failed to bind value of " +
container.getName() +
" to field " +
fieldName +
" in " +
parentType +
": neither field nor getter/setter were found."
);
}
}
field = tmpField;
getter = tmpGetter;
setter = tmpSetter;
fieldType = tmpFieldType;
}
if(fieldType == null)
{
this.javaType = javaType == null ? String.class : javaType;
}
else
{
if(javaType == null)
{
this.javaType = fieldType;
}
else if(Collection.class == fieldType ||
Collection.class.isAssignableFrom(fieldType) ||
fieldType.isAssignableFrom(javaType))
{
this.javaType = javaType;
}
else
{
throw new JBossXBRuntimeException("Failed to bind value of " +
container.getName() +
" to field " +
fieldName +
" in " +
parentType +
": field type " + fieldType + " is not assignable from the specified Java type " + javaType
);
}
if(this.javaType.isInterface() || Modifier.isAbstract(this.javaType.getModifiers()))
{
throw new JBossXBRuntimeException("Failed to bind value of " +
container.getName() +
" to field " +
fieldName +
" in " +
parentType +
": Java type is abstract class or interface."
);
}
}
}
protected Field getFieldLocal()
{
return field;
}
protected Method getGetterLocal()
{
return getter;
}
protected Method getSetterLocal()
{
return setter;
}
protected Class getFieldTypeLocal()
{
return fieldType;
}
protected XmlValueBinding getValueLocal()
{
return null;
}
protected Class getJavaTypeLocal()
{
return javaType;
}
}
// Stack impl
class DocumentBindingStackImpl
implements DocumentBindingStack
{
private final List stack = new ArrayList();
private final Map namespaces = new HashMap();
public DocumentBindingStackImpl()
{
}
public DocumentBindingStackImpl(DocumentBinding doc)
{
if(doc != null)
{
push(doc);
}
}
void push(DocumentBinding doc)
{
stack.add(doc);
}
NamespaceBindingStack bindNamespace(NamespaceBinding ns)
{
NamespaceBindingStack stack = (NamespaceBindingStack)getNamespace(ns.getNamespaceUri());
if(stack == null)
{
stack = new NamespaceBindingStack(this, ns.getNamespaceUri());
namespaces.put(ns.getNamespaceUri(), stack);
}
stack.push(ns);
return stack;
}
public NamespaceBinding getNamespace(String namespaceUri)
{
NamespaceBindingStack nsStack = (NamespaceBindingStack)namespaces.get(namespaceUri);
if(nsStack == null)
{
nsStack = new NamespaceBindingStack(this, namespaceUri);
for(int i = 0; i < stack.size(); ++i)
{
AbstractDocumentBinding doc = (AbstractDocumentBinding)stack.get(i);
NamespaceBinding local = doc.getNamespaceLocal(namespaceUri);
if(local != null)
{
nsStack.push(local);
}
}
if(nsStack.delegates.size() > 0)
{
namespaces.put(namespaceUri, nsStack);
}
else
{
nsStack = null;
}
}
return nsStack;
}
public DocumentBindingStack push(Class documentBindingClass)
{
pushNewDocumentBinding(documentBindingClass);
return this;
}
public DocumentBindingStack push(Class documentBindingClass, DocumentBinder binder)
{
DocumentBinding newDoc = pushNewDocumentBinding(documentBindingClass);
binder.bind(newDoc);
return this;
}
// Private
private DocumentBinding pushNewDocumentBinding(Class documentBindingClass)
{
if(!DocumentBinding.class.isAssignableFrom(documentBindingClass))
{
throw new JBossXBRuntimeException("The class must implement " + DocumentBinding.class);
}
DocumentBinding doc;
try
{
Constructor ctor = documentBindingClass.getConstructor(new Class[]{DocumentBinding.class});
doc = (DocumentBinding)ctor.newInstance(new Object[]{this});
push(doc);
}
catch(NoSuchMethodException e)
{
throw new JBossXBRuntimeException(
"The class must have a constructor with one parameter of type " + DocumentBinding.class
);
}
catch(Exception e)
{
throw new JBossXBRuntimeException("Failed to create an instance of " +
documentBindingClass +
" using constructor which takes one parameter of type " +
DocumentBinding.class +
": " +
e.getMessage()
);
}
return doc;
}
}
class NamespaceBindingStack
implements NamespaceBinding
{
private final DocumentBinding doc;
private final String namespaceUri;
private final List delegates = new ArrayList();
private final Map tops = new HashMap();
public NamespaceBindingStack(DocumentBinding doc, String namespaceUri)
{
this.doc = doc;
this.namespaceUri = namespaceUri;
}
TopElementBinding bindTopElement(TopElementBinding top)
{
TopElementBindingStack stack = (TopElementBindingStack)getTopElement(top.getName().getLocalPart());
if(stack == null)
{
stack = new TopElementBindingStack(doc, top.getName());
tops.put(stack.getName().getLocalPart(), stack);
}
stack.push(top);
return stack;
}
void push(NamespaceBinding ns)
{
delegates.add(ns);
}
public String getNamespaceUri()
{
return namespaceUri;
}
public String getJavaPackage()
{
return ((AbstractNamespaceBinding)delegates.get(delegates.size() - 1)).getJavaPackageLocal();
}
public DocumentBinding getDocument()
{
return doc;
}
public TopElementBinding getTopElement(String elementName)
{
TopElementBindingStack stack = (TopElementBindingStack)tops.get(elementName);
if(stack == null)
{
stack = new TopElementBindingStack(doc, new QName(namespaceUri, elementName));
for(int i = 0; i < delegates.size(); ++i)
{
AbstractNamespaceBinding ns = (AbstractNamespaceBinding)delegates.get(i);
TopElementBinding local = ns.getTopElementLocal(elementName);
if(local != null)
{
stack.push(local);
}
}
if(stack.delegates.size() > 0)
{
tops.put(elementName, stack);
}
else
{
stack = null;
}
}
return stack;
}
}
class XmlValueContainerStack
implements XmlValueContainer
{
protected final QName name;
final List delegates = new ArrayList();
private XmlValueBindingStack value;
public XmlValueContainerStack(QName name)
{
this.name = name;
}
void push(XmlValueContainer container)
{
delegates.add(container);
}
XmlValueBinding bindValue(XmlValueBinding value)
{
getValue();
if(this.value == null)
{
this.value = new XmlValueBindingStack(this);
}
this.value.push(value);
return this.value;
}
public QName getName()
{
return name;
}
public XmlValueBinding getValue()
{
if(value == null)
{
for(int i = 0; i < delegates.size(); ++i)
{
AbstractXmlValueContainer basic = (AbstractXmlValueContainer)delegates.get(i);
XmlValueBinding localValue = basic.getValueLocal();
if(localValue != null)
{
if(value == null)
{
value = new XmlValueBindingStack(this);
}
value.push(localValue);
}
}
}
return value;
}
public Class getJavaType()
{
return ((AbstractXmlValueContainer)delegates.get(delegates.size() - 1)).getJavaTypeLocal();
}
}
class BasicElementBindingStack
extends XmlValueContainerStack
implements BasicElementBinding
{
private final DocumentBinding doc;
private final Map children = new HashMap();
private final Map attributes = new HashMap();
public BasicElementBindingStack(DocumentBinding doc, QName elementName)
{
super(elementName);
this.doc = doc;
}
ElementBinding bindChild(ElementBinding element)
{
ElementBindingStack stack = (ElementBindingStack)getElement(element.getName());
if(stack == null)
{
stack = new ElementBindingStack(doc, element.getName());
children.put(stack.getName(), stack);
}
stack.push(element);
return stack;
}
void bindAttribute(AttributeBinding attr)
{
attributes.put(attr.getAttributeName(), attr);
}
public DocumentBinding getDocument()
{
return doc;
}
public ElementBinding getElement(QName elementName)
{
ElementBindingStack stack = (ElementBindingStack)children.get(elementName);
if(stack == null)
{
stack = new ElementBindingStack(doc, elementName);
for(int i = 0; i < delegates.size(); ++i)
{
AbstractBasicElementBinding el = (AbstractBasicElementBinding)delegates.get(i);
ElementBinding local = el.getElementLocal(elementName);
if(local != null)
{
stack.push(local);
}
}
if(stack.delegates.size() > 0)
{
children.put(elementName, stack);
}
else
{
stack = null;
}
}
return stack;
}
public AttributeBinding getAttribute(QName attributeName)
{
AttributeBinding attr = (AttributeBinding)attributes.get(attributeName);
if(attr == null)
{
for(int i = delegates.size() - 1; i >= 0; --i)
{
AbstractBasicElementBinding basic = (AbstractBasicElementBinding)delegates.get(i);
attr = basic.getAttributeLocal(attributeName);
if(attr != null)
{
break;
}
}
}
return attr;
}
}
class TopElementBindingStack
extends BasicElementBindingStack
implements TopElementBinding
{
public TopElementBindingStack(DocumentBinding doc, QName elementName)
{
super(doc, elementName);
}
}
class ElementBindingStack
extends BasicElementBindingStack
implements ElementBinding
{
public ElementBindingStack(DocumentBinding doc, QName elementName)
{
super(doc, elementName);
}
public Field getField()
{
return getLatestBinding().getFieldLocal();
}
public Method getGetter()
{
return getLatestBinding().getGetterLocal();
}
public Method getSetter()
{
return getLatestBinding().getSetterLocal();
}
public Class getFieldType()
{
return getLatestBinding().getFieldTypeLocal();
}
// Private
protected AbstractElementBinding getLatestBinding()
{
return (AbstractElementBinding)delegates.get(delegates.size() - 1);
}
}
class XmlValueBindingStack
extends XmlValueContainerStack
implements XmlValueBinding
{
public XmlValueBindingStack(XmlValueContainer container)
{
super(container.getName());
}
public Field getField()
{
return getLatestBinding().getFieldLocal();
}
public Method getGetter()
{
return getLatestBinding().getGetterLocal();
}
public Method getSetter()
{
return getLatestBinding().getSetterLocal();
}
public Class getFieldType()
{
return getLatestBinding().getFieldTypeLocal();
}
private AbstractXmlValueBinding getLatestBinding()
{
return (AbstractXmlValueBinding)delegates.get(delegates.size() - 1);
}
}
}