/*
 * Copyright (c) 1998-2007 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Emil Ong, Adam Megacz
 */

package com.caucho.jaxb;

import com.caucho.jaxb.skeleton.*;
import com.caucho.jaxb.property.*;
import com.caucho.server.util.CauchoSystem;
import com.caucho.util.L10N;
import com.caucho.xml.QNode;

import org.w3c.dom.Node;

import javax.activation.DataHandler;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.datatype.*;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Source;
import javax.xml.transform.Result;
import java.awt.Image;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.util.*;

/**
 * Entry point to API
 */
public class JAXBContextImpl extends JAXBContext {
  public static final String XML_SCHEMA_NS = "http://www.w3.org/2001/XMLSchema";

  public static final String TARGET_NAMESPACE = 
    "com.caucho.jaxb.targetNamespace";

  static final ValidationEventHandler DEFAULT_VALIDATION_EVENT_HANDLER
    = new DefaultValidationEventHandler();

  private static final L10N L = new L10N(JAXBContextImpl.class);

  private static final HashSet<Class> _specialClasses = new HashSet<Class>();

  static {
    _specialClasses.add(Object.class);
    _specialClasses.add(Class.class);
    _specialClasses.add(String.class);
    _specialClasses.add(Double.class);
    _specialClasses.add(Float.class);
    _specialClasses.add(Integer.class);
    _specialClasses.add(Long.class);
    _specialClasses.add(Boolean.class);
    _specialClasses.add(Character.class);
    _specialClasses.add(Short.class);
    _specialClasses.add(Byte.class);
    _specialClasses.add(BigDecimal.class);
    _specialClasses.add(BigInteger.class);
    _specialClasses.add(QName.class);
    _specialClasses.add(Date.class);
    _specialClasses.add(Calendar.class);
    _specialClasses.add(XMLGregorianCalendar.class);
    _specialClasses.add(UUID.class);
    _specialClasses.add(DataHandler.class);
  }

  private String _targetNamespace = null;

  private String[] _packages;
  private JAXBIntrospector _jaxbIntrospector;

  private XMLInputFactory _staxInputFactory;
  private XMLOutputFactory _staxOutputFactory;

  private final ArrayList<ObjectFactorySkeleton> _objectFactories 
    = new ArrayList<ObjectFactorySkeleton>();

  private final HashMap<String,Object> _properties 
    = new HashMap<String,Object>();

  private final LinkedHashMap<Class,ClassSkeleton> _classSkeletons 
    = new LinkedHashMap<Class,ClassSkeleton>();

  private final LinkedHashMap<Class,JAXBElementSkeleton> _jaxbElementSkeletons 
    = new LinkedHashMap<Class,JAXBElementSkeleton>();

  private final HashMap<QName,ClassSkeleton> _roots 
    = new HashMap<QName,ClassSkeleton>();
  private final HashMap<QName,ClassSkeleton> _types 
    = new HashMap<QName,ClassSkeleton>();

  private final ArrayList<EnumProperty> _enums = new ArrayList<EnumProperty>();

  private HashSet<Class> _pendingSkeletons = new HashSet<Class>();

  private DynamicJAXBElementSkeleton _dynamicSkeleton;
  private Property _laxAnyTypeProperty = null;

  private boolean _isDiscoveryFinished = false;

  public static JAXBContext createContext(String contextPath,
                                          ClassLoader classLoader,
                                          Map<String,?> properties)
    throws JAXBException
  {
    return new JAXBContextImpl(contextPath, classLoader, properties);
  }

  public JAXBContextImpl(String contextPath,
                         ClassLoader classLoader,
                         Map<String,?> properties)
    throws JAXBException
  {
    _jaxbIntrospector = new JAXBIntrospectorImpl(this);
    StringTokenizer st = new StringTokenizer(contextPath, ":");

    if (properties != null)
      _targetNamespace = (String) properties.get(TARGET_NAMESPACE);

    do {
      String packageName = st.nextToken(); 
      loadPackage(packageName, classLoader);
    } 
    while (st.hasMoreTokens());

    init(properties);
  }

  public static JAXBContext createContext(Class []classes, 
                                          Map<String,?> properties)
    throws JAXBException
  {
    return new JAXBContextImpl(classes, properties);
  }

  public JAXBContextImpl(Class[] classes, Map<String,?> properties)
    throws JAXBException
  {
    _jaxbIntrospector = new JAXBIntrospectorImpl(this);
    _packages = new String[0];

    if (properties != null)
      _targetNamespace = (String) properties.get(TARGET_NAMESPACE);

    for(Class c : classes) {
      if (c.getName().endsWith(".ObjectFactory"))
        introspectObjectFactory(c);
      else if (! c.isPrimitive() && // XXX pull out to JAX-WS?
          ! c.isArray() && 
          ! _specialClasses.contains(c))
        createSkeleton(c);
    }

    init(properties);
  }

  private void init(Map<String,?> properties)
    throws JAXBException
  {
    if (properties != null) {
      for(Map.Entry<String,?> e : properties.entrySet())
        setProperty(e.getKey(), e.getValue());
    }

    DatatypeConverter.setDatatypeConverter(new DatatypeConverterImpl());

    _dynamicSkeleton = new DynamicJAXBElementSkeleton(this);

    _isDiscoveryFinished = true;

    for (ClassSkeleton skeleton : _classSkeletons.values())
      skeleton.postProcess();
  }

  public boolean isDiscoveryFinished()
  {
    return _isDiscoveryFinished;
  }

  public Marshaller createMarshaller()
    throws JAXBException
  {
    return new MarshallerImpl(this);
  }

  public Unmarshaller createUnmarshaller()
    throws JAXBException
  {
    return new UnmarshallerImpl(this);
  }

  public Validator createValidator()
    throws JAXBException
  {
    throw new UnsupportedOperationException();
  }

  XMLStreamReader getXMLStreamReader(InputStream is)
    throws XMLStreamException
  {
    if (_staxInputFactory == null)
      _staxInputFactory = XMLInputFactory.newInstance();

    return _staxInputFactory.createXMLStreamReader(is);
  }

  public XMLInputFactory getXMLInputFactory()
  {
    if (_staxInputFactory == null)
      _staxInputFactory = XMLInputFactory.newInstance();

    return _staxInputFactory;
  }

  public XMLOutputFactory getXMLOutputFactory()
  {
    if (_staxOutputFactory == null) {
      _staxOutputFactory = XMLOutputFactory.newInstance();
      _staxOutputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES,
                                     Boolean.TRUE);
    }

    return _staxOutputFactory;
  }

  public String getTargetNamespace()
  {
    return _targetNamespace;
  }

  public String toString() 
  {
    StringBuilder sb = new StringBuilder();
    sb.append("JAXBContext[");

    for (Class c : _classSkeletons.keySet())
      sb.append(c.getName() + ":");

    for (int i = 0; i < _packages.length; i++) {
      String p = _packages[i];
      sb.append(p + (i < _packages.length - 1 ? ":" : ""));
    }

    sb.append("]");
    return sb.toString();
  }

  private void setProperty(String key, Object val)
  {
    _properties.put(key, val);
  }

  public Binder<Node> createBinder()
  {
    return (Binder<Node>) new BinderImpl(this);
  }

  public <T> Binder<T> createBinder(Class<T> domType)
  {
    if (! domType.equals(QNode.class))
      throw new UnsupportedOperationException("Unsupported implementation: " + 
                                              domType);

    return (Binder) new BinderImpl(this);
  }
  
  public JAXBIntrospector createJAXBIntrospector()
  {
    return _jaxbIntrospector;
  }

  public void generateSchema(SchemaOutputResolver outputResolver)
    throws IOException
  {
    Result result = outputResolver.createOutput("", "schema1.xsd");

    XMLStreamWriter out = null;
    
    try {
      XMLOutputFactory factory = getXMLOutputFactory();
      out = factory.createXMLStreamWriter(result);

      out.writeStartDocument("UTF-8", "1.0");

      out.writeStartElement("xsd", "schema", XML_SCHEMA_NS);
      out.writeAttribute("version", "1.0");

      generateSchemaWithoutHeader(out);

      out.writeEndElement(); // schema
    }
    catch (Exception e) {
      IOException ioException = new IOException();

      ioException.initCause(e);

      throw ioException;
    }
    finally {
      try {
        out.close();
      }
      catch (XMLStreamException e) {
        throw new IOException(e.toString());
      }
    }
  }

  public void generateSchemaWithoutHeader(XMLStreamWriter out)
    throws JAXBException, XMLStreamException
  {
    for (ClassSkeleton skeleton : _classSkeletons.values())
      skeleton.generateSchema(out);

    for (int i = 0; i < _enums.size(); i++)
      ((EnumProperty) _enums.get(i)).generateSchema(out);
  }

  public ClassSkeleton createSkeleton(Class c)
    throws JAXBException
  {
    ClassSkeleton skeleton = _classSkeletons.get(c);

    if (skeleton != null)
      return skeleton;

    if (Object.class.equals(c)) {
      skeleton = new AnyTypeSkeleton(this);
      _classSkeletons.put(c, skeleton);
    }
    else {
      // XXX
      if (c.isEnum() || c.isInterface()) {
	return null;
	/*
        throw new IllegalStateException(L.l("{0}: Can't create skeleton for an interface or enum",
					    c.getName()));
	*/
      }

      skeleton = new ClassSkeleton(this, c);

      // Breadcrumb to prevent problems with recursion
      _classSkeletons.put(c, skeleton); 

      _pendingSkeletons.add(c);

      skeleton.init();

      _pendingSkeletons.remove(c);
    }

    return skeleton;
  }

  public ClassSkeleton getSkeleton(Class c)
    throws JAXBException
  {
    return createSkeleton(c);
  }

  public boolean hasSkeleton(Class c)
  {
    return _classSkeletons.containsKey(c);
  }

  public ClassSkeleton findSkeletonForClass(Class cl)
    throws JAXBException
  {
    return findSkeletonForClass(cl, _jaxbElementSkeletons);
  }

  public ClassSkeleton 
    findSkeletonForClass(Class cl, Map<Class,? extends ClassSkeleton> map)
  {
    Class givenClass = cl;

    while (! cl.equals(Object.class)) {
      ClassSkeleton skeleton = map.get(cl);

      if (skeleton != null)
        return skeleton;

      cl = cl.getSuperclass();
    }

    return null;
  }

  public ClassSkeleton findSkeletonForObject(Object obj)
  {
    if (obj instanceof JAXBElement) {
      JAXBElement element = (JAXBElement) obj;

      obj = element.getValue();

      ClassSkeleton skeleton = 
        findSkeletonForClass(obj.getClass(), _jaxbElementSkeletons);

      if (skeleton == null)
        skeleton = _dynamicSkeleton;
      
      return skeleton;
    }
    else
      return findSkeletonForClass(obj.getClass(), _classSkeletons);
  }

  /**
   * Finds all ClassSkeletons that are subclasses of the given class and
   * are root elements.
   **/
  public List<ClassSkeleton> getRootElements(Class cl)
  {
    ArrayList<ClassSkeleton> list = new ArrayList<ClassSkeleton>();

    for (Map.Entry<Class,ClassSkeleton> entry : _classSkeletons.entrySet()) {
      if (cl.isAssignableFrom(entry.getKey()) && 
          entry.getValue().isRootElement())
        list.add(entry.getValue());
    }

    return list;
  }

  public Property getLaxAnyTypeProperty()
    throws JAXBException
  {
    if (_laxAnyTypeProperty == null)
      _laxAnyTypeProperty = new LaxAnyTypeProperty(this);

    return _laxAnyTypeProperty;
  }

  // XXX clean up all this argument tiering

  public Property createProperty(Type type)
    throws JAXBException
  {
    return createProperty(type, false);
  }

  public Property createProperty(Type type, boolean anyType)
    throws JAXBException
  {
    return createProperty(type, anyType, null);
  }

  public Property createProperty(Type type, boolean anyType, String mimeType)
    throws JAXBException
  {
    return createProperty(type, anyType, mimeType, false);
  }

  public Property createProperty(Type type, boolean anyType, String mimeType, 
                                 boolean xmlList)
    throws JAXBException
  {
    return createProperty(type, anyType, mimeType, xmlList, false);
  }

  public Property createProperty(Type type, boolean anyType, String mimeType, 
                                 boolean xmlList, boolean xmlValue)
    throws JAXBException
  {
    if (type instanceof Class) {
      if (anyType && Object.class.equals(type))
        return getLaxAnyTypeProperty();

      Property simpleTypeProperty = 
        getSimpleTypeProperty((Class) type, mimeType);

      if (simpleTypeProperty != null) {
        // jaxb/12gb
        if (simpleTypeProperty == ByteArrayProperty.PROPERTY && 
            xmlList && ! xmlValue)
          throw new JAXBException(L.l("@XmlList applied to byte[] valued fields or properties"));

        return simpleTypeProperty;
      }

      Class cl = (Class) type;

      if (cl.isArray()) {
        Property componentProperty = 
          createProperty(cl.getComponentType(), anyType);

        if (xmlList) {
          if (! (componentProperty instanceof CDataProperty))
            throw new JAXBException(L.l("Elements annotated with @XmlList or @XmlValue must be simple XML types"));

          Class componentType = cl.getComponentType();
          CDataProperty cdataProperty = (CDataProperty) componentProperty;

          return XmlListArrayProperty.createXmlListArrayProperty(cdataProperty,
                                                                 componentType);
        }
        else 
          return ArrayProperty.createArrayProperty(componentProperty,
                                                   cl.getComponentType());
      }

      if (cl.isEnum()) {
        EnumProperty enumProperty = new EnumProperty(cl, this);
        _enums.add(enumProperty);

        return enumProperty;
      }

      // XXX Map

      if (List.class.isAssignableFrom(cl)) {
        Property property = new SkeletonProperty(getSkeleton(Object.class));

        if (xmlList) {
          throw new JAXBException(L.l("Elements annotated with @XmlList or @XmlValue must be simple XML types"));
        }
        else
          return new ListProperty(property);
      }

      if (Collection.class.isAssignableFrom(cl)) {
        Property property = new SkeletonProperty(getSkeleton(Object.class));

        if (xmlList) {
          throw new JAXBException(L.l("Elements annotated with @XmlList or @XmlValue must be simple XML types"));
        }
        else
          return new CollectionProperty(property);
      }

      ClassSkeleton skel = getSkeleton(cl);

      // XXX: interfaces
      if (skel != null)
	return new SkeletonProperty(skel);
      else
	return null;
    }
    else if (type instanceof ParameterizedType) {
      ParameterizedType ptype = (ParameterizedType) type;
      Type rawType = ptype.getRawType();

      if (rawType instanceof Class) {
        Class rawClass = (Class) rawType;

        if (Map.class.isAssignableFrom(rawClass)) {
          Type[] args = ptype.getActualTypeArguments();

          if (args.length != 2)
            throw new JAXBException(L.l("unexpected number of generic arguments for Map<>: {0}", args.length));

          Property keyProperty = createProperty(args[0], anyType);
          Property valueProperty = createProperty(args[1], anyType);

          return new MapProperty(rawClass, keyProperty, valueProperty);
        }

        if (Collection.class.isAssignableFrom(rawClass)) {
          Type[] args = ptype.getActualTypeArguments();

          if (args.length != 1)
            throw new JAXBException(L.l("unexpected number of generic arguments for List<>: {0}", args.length));

          Property componentProperty = createProperty(args[0], anyType);

          if (xmlList) {
            if (! (componentProperty instanceof CDataProperty))
              throw new JAXBException(L.l("Elements annotated with @XmlList or @XmlValue must be simple XML types"));

            CDataProperty cdataProperty = (CDataProperty) componentProperty;

            return new XmlListCollectionProperty(cdataProperty, rawClass);
          }
          else if (List.class.isAssignableFrom(rawClass))
            return new ListProperty(componentProperty);
          else
            return new CollectionProperty(componentProperty);
        }
      }
    }
    else if (type instanceof GenericArrayType) {
      Type component = ((GenericArrayType) type).getGenericComponentType();

      if (byte.class.equals(component))
        return ByteArrayProperty.PROPERTY;

      // XXX other component types?
    }

    throw new JAXBException(L.l("Unrecognized type: {0}", type.toString()));
  }

  public Property getSimpleTypeProperty(Class type)
    throws JAXBException
  {
    return getSimpleTypeProperty(type, null);
  }

  public Property getSimpleTypeProperty(Class type, String mimeType)
    throws JAXBException
  {
    if (String.class.equals(type))
      return StringProperty.PROPERTY;

    if (URI.class.equals(type))
      return URIProperty.PROPERTY;

    if (UUID.class.equals(type))
      return UUIDProperty.PROPERTY;

    if (Double.class.equals(type))
      return DoubleProperty.OBJECT_PROPERTY;

    if (Double.TYPE.equals(type))
      return DoubleProperty.PRIMITIVE_PROPERTY;

    if (Float.class.equals(type))
      return FloatProperty.OBJECT_PROPERTY;

    if (Float.TYPE.equals(type))
      return FloatProperty.PRIMITIVE_PROPERTY;

    if (Integer.class.equals(type))
      return IntProperty.OBJECT_PROPERTY;

    if (Integer.TYPE.equals(type))
      return IntProperty.PRIMITIVE_PROPERTY;

    if (Long.class.equals(type))
      return LongProperty.OBJECT_PROPERTY;

    if (Long.TYPE.equals(type))
      return LongProperty.PRIMITIVE_PROPERTY;

    if (Boolean.class.equals(type))
      return BooleanProperty.OBJECT_PROPERTY;

    if (Boolean.TYPE.equals(type))
      return BooleanProperty.PRIMITIVE_PROPERTY;

    if (Character.class.equals(type))
      return CharacterProperty.OBJECT_PROPERTY;

    if (Character.TYPE.equals(type))
      return CharacterProperty.PRIMITIVE_PROPERTY;

    if (Short.class.equals(type))
      return ShortProperty.OBJECT_PROPERTY;

    if (Short.TYPE.equals(type))
      return ShortProperty.PRIMITIVE_PROPERTY;

    if (Byte.class.equals(type))
      return ByteProperty.OBJECT_PROPERTY;

    if (Byte.TYPE.equals(type))
      return ByteProperty.PRIMITIVE_PROPERTY;

    if (BigDecimal.class.equals(type))
      return BigDecimalProperty.PROPERTY;

    if (BigInteger.class.equals(type))
      return BigIntegerProperty.PROPERTY;

    if (QName.class.equals(type))
      return QNameProperty.PROPERTY;

    if (Date.class.equals(type))
      return DateTimeProperty.PROPERTY;

    if (Calendar.class.equals(type))
      return CalendarProperty.PROPERTY;

    if (Duration.class.equals(type))
      return DurationProperty.PROPERTY;

    if (XMLGregorianCalendar.class.equals(type))
      return XMLGregorianCalendarProperty.PROPERTY;

    if (Image.class.equals(type))
      return ImageProperty.getImageProperty(mimeType);

    if (DataHandler.class.equals(type))
      return DataHandlerProperty.PROPERTY;

    if (Source.class.equals(type))
      return SourceProperty.PROPERTY;

    if (byte[].class.equals(type))
      return ByteArrayProperty.PROPERTY;

    return null;
  }

  public void addXmlType(QName typeName, ClassSkeleton skeleton)
    throws JAXBException
  {
    if (_types.containsKey(typeName)) {
      ClassSkeleton existing = _types.get(typeName);

      if (! _pendingSkeletons.contains(existing.getType())) {
        throw new JAXBException(L.l("Duplicate type name {0} for types {1} and {2}",
                                    typeName,
                                    skeleton.getType(),
                                    existing.getType()));
      }
    }

    _types.put(typeName, skeleton);
  }

  public void addRootElement(ClassSkeleton s) 
    throws JAXBException
  {
    ClassSkeleton old = _roots.get(s.getElementName());

    // Use != here to check duplicate puts; equals() isn't necessary
    if (old != null && old != s && ! _pendingSkeletons.contains(s.getType())) {
      throw new JAXBException(L.l("Duplicate name {0} for classes {1} and {2}",
                                  s.getElementName(),
                                  s.getType(),
                                  _roots.get(s.getElementName()).getType()));
    }

    _roots.put(s.getElementName(), s);
  }

  public boolean hasXmlType(QName typeName)
  {
    return _types.containsKey(typeName);
  }

  public boolean hasRootElement(QName elementName)
  {
    return _roots.containsKey(elementName);
  }

  public ClassSkeleton getRootElement(QName q)
  {
    return _roots.get(q);
  }

  private void loadPackage(String packageName, ClassLoader classLoader) 
    throws JAXBException
  {
    boolean success = false;

    try {
      Class cl = Class.forName(packageName + ".ObjectFactory");
      introspectObjectFactory(cl);

      success = true;
    }
    catch (ClassNotFoundException e) {
      // we can still try for jaxb.index
    }

    String resourceName = packageName.replace('.', '/') + "/jaxb.index";

    // For some reason, this approach works when running resin...
    InputStream is = this.getClass().getResourceAsStream('/' + resourceName);

    // ...and this approach works in QA
    if (is == null)
      is = classLoader.getResourceAsStream(resourceName);

    if (is == null) {
      if (success)
        return;

      throw new JAXBException(L.l("Unable to open jaxb.index for package {0}",
                                  packageName));
    }

    try {
      InputStreamReader isr = new InputStreamReader(is, "utf-8");
      LineNumberReader in = new LineNumberReader(isr);

      for (String line = in.readLine();
           line != null;
           line = in.readLine()) {
        String[] parts = line.split("#", 2);
        String className = parts[0].trim();

        if (! "".equals(className)) {
          Class cl = classLoader.loadClass(packageName + "." + className);

          createSkeleton(cl);
        }
      }
    }
    catch (IOException e) {
      throw new JAXBException(L.l("Error while reading jaxb.index for package {0}", packageName), e);
    }
    catch (ClassNotFoundException e) {
      throw new JAXBException(e);
    }
  }

  private void introspectObjectFactory(Class factoryClass)
    throws JAXBException
  {
    Object objectFactory = null;

    try {
      objectFactory = factoryClass.newInstance();
    }
    catch (Exception e) {
      throw new JAXBException(e);
    }

    String namespace = null;
    Package pkg = factoryClass.getPackage();

    if (pkg.isAnnotationPresent(XmlSchema.class)) {
      XmlSchema schema = (XmlSchema) pkg.getAnnotation(XmlSchema.class);

      if (! "".equals(schema.namespace()))
        namespace = schema.namespace();
    }

    Method[] methods = factoryClass.getMethods();

    for (Method method : methods) {
      if (method.getName().startsWith("create")) {
        XmlElementDecl decl = method.getAnnotation(XmlElementDecl.class);
        Class cl = method.getReturnType();

        ClassSkeleton skeleton = null;
        QName root = null;

        if (cl.equals(JAXBElement.class)) {
          ParameterizedType type = 
            (ParameterizedType) method.getGenericReturnType();
          cl = (Class) type.getActualTypeArguments()[0];

          skeleton = new JAXBElementSkeleton(this, cl, method, objectFactory);
          _jaxbElementSkeletons.put(cl, (JAXBElementSkeleton) skeleton);
        }
        else {
          skeleton = getSkeleton(cl);

          if (skeleton == null) 
            skeleton = createSkeleton(cl);

          skeleton.setCreateMethod(method, objectFactory);

          root = skeleton.getElementName();
        }

        if (decl != null) {
          String localName = decl.name();

          if (! "##default".equals(decl.namespace()))
            namespace = decl.namespace();

          if (namespace == null)
            root = new QName(localName);
          else
            root = new QName(namespace, localName);
        }

        skeleton.setElementName(root);
        addRootElement(skeleton);
      }
      else if (method.getName().equals("newInstance")) {
        // XXX
      }
      else if (method.getName().equals("getProperty")) {
        // XXX
      }
      else if (method.getName().equals("setProperty")) {
        // XXX
      }
    }
  }

  static class DefaultValidationEventHandler implements ValidationEventHandler {
    public boolean handleEvent(ValidationEvent event)
    {
      if (event == null)
        throw new IllegalArgumentException("Event may not be null");

      return event.getSeverity() != ValidationEvent.FATAL_ERROR;
    }
  }
}

