/*
 * Copyright (c) 2003-2006, KNOPFLERFISH project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *
 * - Neither the name of the KNOPFLERFISH project nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @author Erik Wistrand
 * @author Philippe Laporte
 */

//TODO use only one parser...

package org.knopflerfish.util.metatype;

import org.osgi.framework.*;
import org.osgi.service.cm.*;
import org.osgi.service.metatype.*;
import org.osgi.util.tracker.ServiceTracker;

import net.n3.nanoxml.*;

import java.net.URL;
import java.io.*;
import java.util.*;
import java.lang.reflect.Array;

import org.knopflerfish.util.Text;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.kxml2.io.KXmlParser;

/**
 * Helper class which loads (and saves) KF Metatype XML, 
 * as well as the R4 Metatype XML
 *
 * <p>
 * This implementaion uses the nanoxml package for KF Metatype XML, and 
 * kXML for R4 Metatype XML
 * </p>
 * <p>
 * NanoXML is distributed under the zlib/libpng license.<br>
 * See <a href="http://nanoxml.sourceforge.net/orig/copyright.html">http://nanoxml.sourceforge.net/orig/copyright.html</a>
 * for details.<br>
 * The full license text is also include in the kf_metatype bundle jar 
 * </p>
 * Nanoxml is Copyrighted 2000-2002 Marc De Scheemaecker, All Rights
 * Reserved.
 * 
 * kXML notice goes here
 * </p>
 */
public class Loader {
  static final String METATYPE      = "metatype";
  static final String SERVICES      = "services";
  static final String FACTORIES     = "factories";
  static final String VALUES        = "values";
  static final String SCHEMA        = "schema";

  static final String SERVICE_PID   = "service.pid";
  static final String FACTORY_PID   = "factory.pid";

  static final String ITEM          = "item";

  static final String ATTR_PID  = "pid";
  static final String ATTR_DECS = "description";

  static final String ATTR_TYPE      = "type";
  static final String ATTR_NAME      = "name";
  static final String ATTR_BASE      = "base";
  static final String ATTR_VALUE     = "value";
  static final String ATTR_ICONURL   = "iconURL";
  static final String ATTR_MINOCCURS = "minOccurs";
  static final String ATTR_MAXOCCURS = "maxOccurs";

  static final String ATTR_ARRAY     = "array";

  static final String XSD_NS       = "http://www.w3.org/2001/XMLSchema";
  static final String METATYPE_NS  = "http://www.knopflerfish.org/XMLMetatype";

  static final String TAG_ANNOTATION    = "annotation";
  static final String TAG_SIMPLETYPE    = "simpleType";
  static final String TAG_COMPLEXTYPE   = "complexType";
  static final String TAG_ELEMENT       = "element";
  static final String TAG_RESTRICTION   = "restriction";
  static final String TAG_ENUMERATION   = "enumeration";

  static final String TAG_DOCUMENTATION    = "documentation";
  static final String TAG_APPINFO          = "appInfo";
  static final String TAG_SEQUENCE         = "sequence";

  static final String BUNDLE_PROTO = "bundle://";

  /**
   * Load a MetaTypeProvider from an XML file.
   */
  public static MTP loadMTPFromURL(Bundle bundle, URL url) throws IOException {
    InputStream in = null;
    
    try {
      in                = url.openStream();
      IXMLParser parser = XMLParserFactory.createDefaultXMLParser();
      IXMLReader reader = new StdXMLReader(in);
      parser.setReader(reader);
      XMLElement el  = (XMLElement) parser.parse();
      return loadMTP(bundle, url, el);
    } catch (Exception e) {
      e.printStackTrace();
      throw new IOException("Failed to load " + url + " " + e);
    } finally {
      try { in.close(); } catch (Exception ignored) { }
    }

  }

  /**
   * load defaults from an XML file into an MTP
   */
  public static List loadDefaultsFromURL(MTP mtp, URL url) throws IOException {
    InputStream in = null;
    
    try {
      in                = url.openStream();
      IXMLParser parser = XMLParserFactory.createDefaultXMLParser();
      IXMLReader reader = new StdXMLReader(in);
      parser.setReader(reader);
      XMLElement el  = (XMLElement) parser.parse();

      if(isName(el, METATYPE_NS, VALUES)) {
    	  List propList = loadValues(mtp, el);
    	  setDefaultValues(mtp, propList);
    	  return propList;
      } 
      else {
    	  for(Enumeration e = el.enumerateChildren(); e.hasMoreElements(); ) {
    		  XMLElement childEl = (XMLElement)e.nextElement();
    		  if(isName(childEl, METATYPE_NS, VALUES)) {
	    
    			  List propList = loadValues(mtp, childEl);
	    
    			  setDefaultValues(mtp, propList);
	    
    			  return propList;
    		  }
    	  }
      }
      throw new XMLException("No values tag in " + url, el);
    } 
    catch (Exception e) {
      e.printStackTrace();
      throw new IOException("Failed to load " + url + " " + e);
    } 
    finally {
      try { in.close(); } catch (Exception ignored) { }
    }
  }

  /**
   * Load a MetaTypeProvider from an XML "config" element.
   *
   * <ol>
   *  <li>Load all service and factory definitions into
   *      a MetaTypeProvider instance.
   *  <li>Load any default data
   *  <li>Insert default data into definitions in MetaTypeProvider
   *      using the <tt>setDefaultValues</tt> method
   * </ol>
   *
   */
  public static MTP loadMTP(Bundle bundle, URL sourceURL, XMLElement el) {
    
    assertTagName(el, METATYPE_NS, METATYPE);

    CMConfig[] services  = null;
    CMConfig[] factories = null;

    boolean bHasDefValues = false;

    for(Enumeration e = el.enumerateChildren(); e.hasMoreElements(); ) {
      XMLElement childEl = (XMLElement)e.nextElement();
      if(isName(childEl, METATYPE_NS, SERVICES)) {
    	  services = parseServices(childEl, false);
      } 
      else if(isName(childEl, METATYPE_NS, FACTORIES)) {
    	  factories = parseServices(childEl, true);
      } 
      else if(isName(childEl, METATYPE_NS, VALUES)) {
    	  bHasDefValues = true;
      } 
      else if(isName(childEl, XSD_NS, SCHEMA)) {
    	  CMConfig[] any = parseSchema(childEl);
    	  List sa = new ArrayList();
    	  List fa = new ArrayList();
    	  for(int i = 0; i < any.length; i++) {
    		  if(any[i].maxInstances > 1) {
    			  fa.add(any[i]);
    		  } 
    		  else {
    			  sa.add(any[i]);
    		  }
    	  }
    	  services = new CMConfig[sa.size()];
    	  sa.toArray(services);
    	  factories = new CMConfig[fa.size()];
    	  fa.toArray(factories);
      	} 
      else {
    	  throw new XMLException("Unexpected element", el);
      }
    }

    MTP mtp = new MTP(sourceURL.toString());
    mtp.setBundle(bundle);

    // Insert services and factory definition into MTP
    // default values will be default values defined by AD
    for(int i = 0; services != null && i < services.length; i++) {
      OCD ocd = new OCD(services[i].pid, services[i].pid, services[i].desc, sourceURL);
      ocd.maxInstances = 1;
      String iconURL = services[i].iconURL;
      if(iconURL != null) {
    	  try {
    		  if(bundle != null) {
    			  if(iconURL.startsWith("/")) {
    				  iconURL = BUNDLE_PROTO + "$(BID)" + iconURL;
    			  }
    			  iconURL = Text.replace(iconURL, "$(BID)", Long.toString(bundle.getBundleId()));
    		  }
    		  ocd.setIconURL(iconURL);
    	  } 
    	  catch (Exception e) {
    		  System.err.println("Failed to set icon url: " +  e);
    	  }
      }
      for(int j = 0; j < services[i].ads.length; j++) {
    	  ocd.add(services[i].ads[j], 
                                     services[i].ads[j].isOptional() 
		                             ? ObjectClassDefinition.OPTIONAL
		                             : ObjectClassDefinition.REQUIRED);
      }
      mtp.addService(services[i].pid, ocd);
    }
    
    for(int i = 0; factories != null && i < factories.length; i++) {
      OCD ocd = new OCD(factories[i].pid, factories[i].pid, factories[i].desc, sourceURL);
      ocd.maxInstances = factories[i].maxInstances;
      if(factories[i].iconURL != null) {
    	  try {
    		  ocd.setIconURL(factories[i].iconURL);
    	  } 
    	  catch (Exception e) {
    		  System.err.println("Failed to set icon url: "+ e);
    	  }
      }
      for(int j = 0; j < factories[i].ads.length; j++) {
    	  ocd.add(factories[i].ads[j], ObjectClassDefinition.REQUIRED);
      }
      mtp.addFactory(factories[i].pid, ocd);
    }

      
    // Overwrite MTP default values with values found in
    // DEFAULTVALUES section in source XML
    if(bHasDefValues) {
    	for(Enumeration e = el.enumerateChildren(); e.hasMoreElements(); ) {
    		XMLElement childEl = (XMLElement)e.nextElement();
    		if(isName(childEl, METATYPE_NS, VALUES)) {
    			List propList = loadValues(mtp, childEl);
	
    			setDefaultValues(mtp, propList);
    		}
      }
    }
    
    return mtp;
  }


  /**
   * Overwrite default values in MTP using a set of dictionaries.
   *
   * @param mtp MetaTypeProvider containing instances of <tt>AD</tt>
   * @param propList List of Dictionary
   */
  public static void setDefaultValues(MetaTypeProvider mtp, 
				      List propList) {

    for(Iterator it = propList.iterator(); it.hasNext();) {
      Dictionary            props   = (Dictionary)it.next();
      String                pid     = (String)props.get(SERVICE_PID);
      if(pid == null) {
	pid     = (String)props.get("factory.pid");
      }

      ObjectClassDefinition ocd     = null;
      try {
	ocd = mtp.getObjectClassDefinition(pid, null);
      } catch (Exception ignored) {
      }

      if(ocd == null) {
	throw new IllegalArgumentException("No definition for pid '" + pid + "'");
      } else {
	AttributeDefinition[] ads = 
	  ocd.getAttributeDefinitions(ObjectClassDefinition.ALL);

	for(int i = 0; ads != null && i < ads.length; i++) {
	  Object val =  props.get(ads[i].getID());

	  if(!(ads[i] instanceof AD)) {
	    throw new IllegalArgumentException("AttributeDefinitions must be instances of AD, otherwise default values cannot be set");
	  }

	  AD ad = (AD)ads[i];

	  if(val instanceof Vector) {
	    ad.setDefaultValue(toStringArray((Vector)val));
	  } else if(val.getClass().isArray()) {
	    ad.setDefaultValue(toStringArray((Object[])val));
	  } else {
	    ad.setDefaultValue(new String[] { val.toString() });
	  }
	}
      }
    }
  }



  /**
   * @return String (pid) -> Dictionary
   */
  public static List loadValues(MetaTypeProvider mtp, XMLElement el) {

    //    assertTagName(el, DEFAULTVALUES);

    List propList = new ArrayList();

    // String (pid) -> Integer (count)
    Map  countMap = new HashMap();

    for(Enumeration e = el.enumerateChildren(); e.hasMoreElements(); ) {
      XMLElement            childEl = (XMLElement)e.nextElement();
      String                pid     = childEl.getName();
      ObjectClassDefinition ocd     = null;
      

      
      try {
	ocd = mtp.getObjectClassDefinition(pid, null);
      } catch (Exception ignored) {
      }
      if(ocd == null) {
	throw new XMLException("Undefined pid '" + pid + "'", childEl);
      }
      Dictionary props = 
	loadValues(ocd.getAttributeDefinitions(ObjectClassDefinition.ALL), 
		   childEl);
      int maxInstances = 1;
      if(ocd instanceof OCD) {
	maxInstances = ((OCD)ocd).maxInstances;
      }

      Integer count = (Integer)countMap.get(pid);
      if(count == null) {
	count = new Integer(0);
      }
      count = new Integer(count.intValue() + 1);
      if(count.intValue() > maxInstances) {
	throw new XMLException("PID " + pid + " can only have " +
			       maxInstances + " instance(s), found " + 
			       count, el);
      }

      countMap.put(pid, count);
      
      props.put(maxInstances > 1 ? "factory.pid" : SERVICE_PID, pid);
      propList.add(props);
    }

    return propList;
  }

  public static Dictionary loadValues(AttributeDefinition[] attrs, 
				      XMLElement el) {

    if(attrs == null) {
      throw new NullPointerException("attrs array cannot be null");
    }

    Hashtable props = new Hashtable();
    for(Enumeration e = el.enumerateChildren(); e.hasMoreElements(); ) {
      XMLElement            childEl = (XMLElement)e.nextElement();
      String                id      = childEl.getFullName();

      AttributeDefinition attr = null;

      //      System.out.println("load id=" + id);

      for(int i = 0; attr == null && i < attrs.length; i++) {
	//	System.out.println(i + ": " + attrs[i]);
	if(id.equals(attrs[i].getID())) {
	  attr = attrs[i];
	}
      }
      if(attr == null) {
	throw new XMLException("Undefined id '" + id + "'", childEl);
      }
      Object val = loadValue(attr, childEl);
      props.put(id, val);
    }

    // Verify that all attributes are found
    for(int i = 0; i < attrs.length; i++) {
      if(!props.containsKey(attrs[i].getID())) {
	throw new XMLException("Missing attribute id '" + attrs[i].getID() + "'", 
			       el);
      }
    }
    return props;
  }


  /**
   * Load a java object from an XML element using type info in the 
   * specified definition.
   *
   */
  public static Object loadValue(AttributeDefinition attr, XMLElement el) {
    assertTagName(el, attr.getID());

    if(attr.getCardinality() < 0) {
      return loadSequence(attr, el, -attr.getCardinality(), ITEM);
    }

    if(attr.getCardinality() > 0) {
      Vector v = loadSequence(attr, el, -attr.getCardinality(), ITEM);
      Object[] array = 
	(Object[])Array.newInstance(AD.getClass(attr.getType()), v.size());
      v.copyInto(array);

      return array;
    }

    return loadContent(attr, el);
  }

  public static Vector loadSequence(AttributeDefinition attr, 
				    XMLElement el,
				    int max,
				    String tagName) {
    Vector v = new Vector();

    for(Enumeration e = el.enumerateChildren(); e.hasMoreElements(); ) {
      XMLElement            childEl = (XMLElement)e.nextElement();

      assertTagName(childEl, tagName);
      
      v.addElement(loadContent(attr, childEl));
    }

    return v;
  }

  /**
   * Load the contents of a tag into a java object.
   *
   * @param el   element which content should be converted to a java object.
   * @param attr definition defining type.
   */
  public static Object loadContent(AttributeDefinition attr, XMLElement el) {
    String content = el.getContent();

    if(content == null) {
      content = "";
    }
    
    content = content.trim();

    String msg = attr.validate(content);
    if(msg != null && !"".equals(msg)) {
      throw new XMLException("Validation error: '" + msg + "'", el);
    }


    return AD.parseSingle(content, attr.getType());
  }

  /**
   * Parse a services or factories tag info an array of wrapper
   * objects.
   *
   *<p>
   * Children to this tag must all be "xsd:schema"
   *</p>
   */
  static CMConfig[] parseServices(XMLElement el, boolean bFactory) {

    assertTagName(el, METATYPE_NS, bFactory ? FACTORIES : SERVICES);

    List list = new ArrayList();

    for(Enumeration e = el.enumerateChildren(); e.hasMoreElements(); ) {
      XMLElement childEl = (XMLElement)e.nextElement();

      CMConfig[] conf = parseSchema(childEl);
      if(conf.length == 0) {
	throw new XMLException("No elements in schema", childEl);
      }
      conf[0].maxInstances = bFactory ? Integer.MAX_VALUE : 1;
      
      list.add(conf[0]);
    }

    CMConfig[] ads = new CMConfig[list.size()];

    list.toArray(ads);
    return ads;
  }

 /**
   * Parse an XSD schema into a wrapper object for services and factories.
   *
   * <p>
   * Each schema element must contain exacly one child "xsd:complexType"
   * </p>
   */
  private static CMConfig[] parseSchema(XMLElement el) {

    assertTagName(el, XSD_NS, "schema"); 

    /*
    if(el.getChildrenCount() != 1) {
      throw new XMLException("service/factory schema must contain exacly one xsd.complexType", el);
    }
    */

    List v = new ArrayList();

    for(Enumeration e = el.enumerateChildren(); e.hasMoreElements(); ) {
      XMLElement childEl = (XMLElement)e.nextElement();
      
      AD[]      ads = parseComplexType(childEl);
      Annotation an = loadAnnotationFromAny(childEl);
      
      String iconURL = childEl.getAttribute(ATTR_ICONURL);
      if("".equals(iconURL)) {
	iconURL = null;
      }
      int maxOccurs = getInteger(childEl, ATTR_MAXOCCURS, 1);
      
      String name = childEl.getAttribute(ATTR_NAME).toString();
      
      //      System.out.println("load " +  name + ", maxOccurs=" + maxOccurs);

      v.add(new CMConfig(name,
			 ads,
			 an != null ? an.doc : "",
			 iconURL,
			 maxOccurs));
    }

    CMConfig[] r = new CMConfig[v.size()];
    v.toArray(r);
    return r;
  }

  static final String UNBOUNDED = "unbounded";

  static int getInteger(XMLElement el, String attr, int def) {
    String s = el.getAttribute(attr, Integer.toString(def));
    if(UNBOUNDED.equals(s)) {
      return Integer.MAX_VALUE;
    }
    return Integer.parseInt(s);
  }


  /**
   * Parse an XSD complexType info an array of <tt>AttributeDefinition</tt>,
   * definining the metadata for a PID.
   *
   * <p>
   * The name of the complexTyp specifies the metadata PID. 
   * </p>
   *
   * <p>
   * The following child elements are supported:
   * <ul>
   * <li><b>xsd:element</b> - defines a singleton definition
   * <li><b>xsd:sequence</b> - defines a vector or array definition
   * </ul>
   *
   * The names of the elements definines the ID's of the definitions. Each
   * ID must only be present once.
   * </p>
   *
   * @throws XMLException if the <tt>el</tt> tag is not an "xsd:complexType"
   */
  static AD[] parseComplexType(XMLElement el) {
    assertTagName(el, XSD_NS, TAG_COMPLEXTYPE);

    Set list = new HashSet();

    Annotation annotation = null;

    for(Enumeration e = el.enumerateChildren(); e.hasMoreElements(); ) {
      XMLElement childEl = (XMLElement)e.nextElement();
      if(isName(childEl, XSD_NS, TAG_ANNOTATION)) {
	annotation = loadAnnotation(childEl);
      } else {
	try {
	  AD ad = parseAttributeDefinition(childEl);
	  if(list.contains(ad)) {
	    throw new XMLException("Multiple definitions of id '" + ad.getID() + 
				   "'", childEl);
	  }
	  if(ad == null) {
	    throw new XMLException("Null ad", childEl);
	  }
	  list.add(ad);
	} catch (XMLException ex) {
	  System.out.println("Failed in " + el.getFullName() + 
			     ", name=" + el.getAttribute(ATTR_NAME) + 
			     ", line=" + el.getLineNr() + ", " + ex);
	  throw ex;
	}
      }
    }
    
    AD[] ads = new AD[list.size()];
    
    list.toArray(ads);
    return ads;
  }
  
  /**
   * Parse an XSD sequence into an <tt>AttributeDefinition</tt> of either
   * vector or array type.
   *
   * <p>
   * Only one child name "element" is allowed, and this child specifies 
   * the element type of the vector/array.
   * </p>
   *
   * @throws XMLException of element is not an "xsd:sequence"
   */
  static AD parseComplexTypeAttr(XMLElement el) {
    assertTagName(el, XSD_NS, TAG_COMPLEXTYPE);

    AD attr = null;
    for(Enumeration e = el.enumerateChildren(); e.hasMoreElements(); ) {
      XMLElement childEl   = (XMLElement)e.nextElement();

      if(isName(childEl, XSD_NS, TAG_SEQUENCE)) {
	if(attr != null) {
	  throw new XMLException("Only one sequence is allowed in complexType", 
				 childEl);
	}
	attr = parseSequence(childEl, el.getAttribute(ATTR_NAME));
      } else if(isName(childEl, XSD_NS, TAG_RESTRICTION)) {
	System.out.println("skip restriction");
      } else if(isName(childEl, XSD_NS, TAG_ANNOTATION)) {
	// parse later
      }
    }
    if(attr == null) {
      throw new XMLException("No sequence found in complexType", el);
    }

    return addAnnotation(attr, el);
    
  }

  static AD parseSimpleTypeAttr(XMLElement el) {
    assertTagName(el, XSD_NS, TAG_SIMPLETYPE);

    AD attr = null;

    String id      = el.getAttribute(ATTR_NAME).toString();

    for(Enumeration e = el.enumerateChildren(); e.hasMoreElements(); ) {
      XMLElement childEl   = (XMLElement)e.nextElement();

      if(isName(childEl, XSD_NS, TAG_RESTRICTION)) {

	int type       = getType(childEl);
		
	int      card     = 0;
	String   name     = id;
	String[] defValue = null;
	
	attr = new AD(id, type, card, name, defValue);
	addEnumeration(childEl, attr);
      } else if(isName(childEl, XSD_NS, TAG_ANNOTATION)) {
	// accept and parse later;
      }
    }

    return addAnnotation(attr, el);
  }

  static void addEnumeration(XMLElement el, AD ad) {
    assertTagName(el, XSD_NS, TAG_RESTRICTION);

    Vector v = new Vector();
    for(Enumeration e = el.enumerateChildren(); e.hasMoreElements(); ) {
      XMLElement childEl   = (XMLElement)e.nextElement();
      //      System.out.println(" addEnum " + childEl.getName());
      if(isName(childEl, XSD_NS, TAG_ENUMERATION)) {
	String val = childEl.getAttribute(ATTR_VALUE);
	if(val == null) {
	  throw new XMLException("No value specified in enum", childEl);
	}
	String label = val;
	Annotation annotation = loadAnnotationFromAny(childEl);
	if(annotation != null && annotation.doc != null) {
	  label = annotation.doc;
	}
	v.addElement(new String[] { val, label });
      }
    }
    
    //    System.out.println("optvalues=" + v);

    if(v.size() > 0) {
      String[] optValues = new String[v.size()];
      String[] optLabels = new String[v.size()];
      for(int i = 0; i < v.size(); i++) {
	String[] row = (String[])v.elementAt(i);
	optValues[i] = row[0];
	optLabels[i] = row[1];
      }
      ad.setOptions(optValues, optLabels);
    }
  }
  static AD addAnnotation(AD attr, 
			  XMLElement el) {
    Annotation a = loadAnnotationFromAny(el);
    if(a != null) {
      if(a.doc != null) {
	attr.setDescription(a.doc);
      }
    }

    int minOccurs = 1;
    try {
      minOccurs = Integer.parseInt(el.getAttribute(ATTR_MINOCCURS, "1"));
    } catch (Exception e) {
      throw new XMLException("minOccurs must be a valid integer: " + e, el);
    }
    if(minOccurs > 1) {
      throw new XMLException("minOccurs cannot be > 1, is " + minOccurs, el);
    }
    attr.bOptional = minOccurs == 0;

    return attr;
  }

  static Annotation loadAnnotationFromAny(XMLElement el) {
    
    for(Enumeration e = el.enumerateChildren(); e.hasMoreElements(); ) {
      XMLElement childEl   = (XMLElement)e.nextElement();
      if(isName(childEl, XSD_NS, TAG_ANNOTATION)) {
	return loadAnnotation(childEl);
      }
    }
    return null;
  }

  static Annotation loadAnnotation(XMLElement el) {

    assertTagName(el, XSD_NS, TAG_ANNOTATION);

    Annotation a = null;
    for(Enumeration e = el.enumerateChildren(); e.hasMoreElements(); ) {
      XMLElement childEl   = (XMLElement)e.nextElement();
      if(isName(childEl, XSD_NS, TAG_DOCUMENTATION)) {
	if(a == null) { a = new Annotation(); }
	a.doc = "" + childEl.getContent();
      } else if(isName(childEl, XSD_NS, TAG_APPINFO)) {
	if(a == null) { a = new Annotation(); }
	a.appinfo = "" + childEl.getContent();
      }
    }
    return a;
  }

  static AD parseSequence(XMLElement el, String name) {
    //    System.out.println("parseSequence " + el.getAttribute(ATTR_NAME));

    assertTagName(el, XSD_NS, TAG_SEQUENCE);


    boolean    bArray    = 
      "true".equals(el.getAttribute(ATTR_ARRAY, "false").toLowerCase());
    
    
    int maxOccurs = getInteger(el, ATTR_MAXOCCURS, Integer.MAX_VALUE);

    if(el.getChildrenCount() != 1) {
      throw new XMLException("sequence children count must be " +
					 "exactly one", el);
    } else {
      String id      = name;
      int type = -1;
      
      for(Enumeration e = el.enumerateChildren(); e.hasMoreElements(); ) {
	XMLElement childEl   = (XMLElement)e.nextElement();
	String     childName = childEl.getAttribute(ATTR_NAME).toString();
	int        card      = -1;
	
	
	
	if(!ITEM.equals(childName)) {
	  throw new XMLException("Only '" + ITEM + "'" + 
				 " names are allowed in sequences, found " + 
				 childName, childEl);
	}
	
	if(bArray) {
	  card = maxOccurs;
	} else {
	  if(maxOccurs == Integer.MAX_VALUE) {
	    card = Integer.MIN_VALUE;
	  } else {
	    card = -maxOccurs;
	  }
	}
	
	AD ad = parseAttributeDefinition(childEl);

	type = ad.getType();

	String[] defValue = null;

	return new AD(id, type, card, id, defValue);	
      }
      
      throw new XMLException("parseSequence failed", el);
    }
  }
  
  
  /**
   * Parse an XSD element into an <tt>AttributeDefinition</tt>
   *
   * <p>
   * The type of the definition is derived using the <tt>getType</tt>
   * method.
   * </p>
   * <p>
   * If the XSD element is a sequence, parse using the <tt>parseSequence</tt>
   * method.
   * </p>
   */
  static AD parseAttributeDefinition(XMLElement el) {
    // System.out.println("parseAttributeDefinition " +   el.getFullName() + ", name=" + el.getAttribute(ATTR_NAME));
    
    AD ad = null;

    if(isName(el, XSD_NS, TAG_SIMPLETYPE)) {
      ad = parseSimpleTypeAttr(el);
    } else if(isName(el, XSD_NS, TAG_COMPLEXTYPE)) {
      ad = parseComplexTypeAttr(el);
    } else if(isName(el, XSD_NS, TAG_ELEMENT)) {
      
      String id      = el.getAttribute(ATTR_NAME);
      if(id == null) {
	throw new XMLException("No id specified in element", el);
      }

      String strType = el.getAttribute(ATTR_TYPE);
      if(strType == null) {
	throw new XMLException("No type specified in " + id, el);
      }
      int type       = getType(el);
      
      
      int      card     = 0;
      String   name     = id;
      String[] defValue = null;
      
      ad = new AD(id, type, card, name, defValue);
    } else if(isName(el, XSD_NS, TAG_ANNOTATION)) {
      //
    } else {
      throw new XMLException("Unsupported tag " + el.getName() +  
			     ", ns=" + el.getNamespace() + 
			     ", name=" + el.getAttribute(ATTR_NAME), el);
    }


    return addAnnotation(ad, el);
  }

  /**
   * Get <tt>AttributeDefinition</tt> type from XSD element type.
   *
   * @return One of the <tt>AttributeDefinition.STRING...BOOLEAN</tt> types.
   * @throws XMLException if the XSD type is unsupported.
   */
  static int getType(XMLElement el) {
    int type = -1;

    String strType = null;
    if(isName(el, XSD_NS, TAG_ELEMENT)) {
      strType = el.getAttribute(ATTR_TYPE);
    } else if(isName(el, XSD_NS, TAG_RESTRICTION)) {
      strType = el.getAttribute(ATTR_BASE);
    } else {
      throw new XMLException("Type is only supported in element and restriction tags", el);
    }
    
    if(strType == null) {
      throw new XMLException("No type in tag", el);
    }


    if("xsd:int".equals(strType)) {
      type = AttributeDefinition.INTEGER;
    } else if("xsd:string".equals(strType)) {
      type = AttributeDefinition.STRING;
    } else if("xsd:boolean".equals(strType)) {
      type = AttributeDefinition.BOOLEAN;
    } else if("xsd:float".equals(strType)) {
      type = AttributeDefinition.FLOAT;
    } else if("xsd:long".equals(strType)) {
      type = AttributeDefinition.LONG;
    } else if("xsd:short".equals(strType)) {
      type = AttributeDefinition.SHORT;
    } else if("xsd:char".equals(strType)) {
      type = AttributeDefinition.CHARACTER;
    } else if("xsd:double".equals(strType)) {
      type = AttributeDefinition.DOUBLE;
    } else {
      throw new XMLException("Unsupported type '" + strType + "'", el);
    }

    return type;
  }




  /**
   * Print sets of definitions to an XML file.
   */
  public static void printMetatypeXML(MetaTypeProvider mtp, 
				      String[] servicePIDs,
				      String[] factoryPIDs,
				      boolean bXMLHeader,
				      boolean bMetatypeTag,
				      List propList,
				      PrintWriter out) {
    if(bXMLHeader) {
      out.println("<?xml version=\"1.0\"?>");
    }

    if(bMetatypeTag) {
      out.println("<metatype:metatype\n" + 
		  "  xmlns:metatype=\"http://www.knopflerfish.org/XMLMetatype\"\n" + 
		  "  xmlns:xsd     = \"http://www.w3.org/2001/XMLSchema\">");    
    }
    out.println("");

    out.println("   <xsd:schema>\n");
    printOCDXML(mtp, servicePIDs, 1, out);
    out.println("");

    printOCDXML(mtp, factoryPIDs, Integer.MAX_VALUE, out);
    out.println("");

    out.println("   </xsd:schema>\n");

    if(propList != null) {
      printValuesXML(propList, false, out);
    }

    if(bMetatypeTag) {
      out.println("</metatype:metatype>");
    }  
  }

  /**
   * Print a set of ObjectClassDefinitions as XML.
   *
   * @param mtp Metatype provider
   * @param pids Set of String (PIDs)
   * @param out writer to print to.
   */
  public static void printOCDXML(MetaTypeProvider mtp, 
				 String[]         pids, 
				 int              maxOccurs,
				 PrintWriter      out) {
    for(int i = 0; i < pids.length; i++) {
      String pid = pids[i];
      ObjectClassDefinition ocd = mtp.getObjectClassDefinition(pid, null);
      if(ocd instanceof OCD) {
	maxOccurs = ((OCD)ocd).maxInstances;
      }
      AttributeDefinition[] ads = 
	ocd.getAttributeDefinitions(ObjectClassDefinition.ALL);
      out.println("");
      out.println("    <!-- " + 
		  (maxOccurs > 1 ? "Factory " : "Service ") + 
		  pid + " -->");
      //      out.println("   <xsd:schema>");
      out.print ("    <xsd:complexType " + ATTR_NAME + "=\"" + pid + "\"");
      out.print (" " + ATTR_MAXOCCURS + "=\"" + 
		 (maxOccurs == Integer.MAX_VALUE 
		  ? UNBOUNDED 
		  : Integer.toString(maxOccurs)) 
		 + "\"");
      
      if(ocd instanceof OCD) {
	OCD o2 = (OCD)ocd;
	String urlStr = o2.getIconURL(0);
	if(urlStr != null) {
	  out.print(" " + ATTR_ICONURL + "=\"" + urlStr + "\"");
	}
      }
      out.println(">");
      printAnnotation(ocd.getDescription(), "     ", out);
      for(int j = 0; j < ads.length; j++) {
	printXML(out, ads[j]);
      }
      out.println("    </xsd:complexType>");
      //      out.println("   </xsd:schema>\n");
    }
  }


  static void printXMLSequence(PrintWriter out, 
			       AttributeDefinition ad,
			       boolean bArray) {
    out.println("     <xsd:complexType " + ATTR_NAME + " = \"" + ad.getID() + "\">");
    out.println("      <xsd:sequence " + ATTR_ARRAY + "=\"" + bArray + "\">");
    out.println("       <xsd:element " + ATTR_NAME + " = \"" + ITEM + 
		"\" " + ATTR_TYPE + "= \"" + 
		getXSDType(ad.getType()) + "\"/>");
    out.println("      </xsd:sequence>");
    out.println("     </xsd:complexType>");

  }
  
  /**
   * Print an attribute definition as XML.
   */
  public static void printXML(PrintWriter out,
			      AttributeDefinition ad) {
    if(ad.getCardinality() > 0) {
      printXMLSequence(out, ad, false);
    } else if(ad.getCardinality() < 0) {
      printXMLSequence(out, ad, true);
    } else {
      printXMLSingle(out, ad);
    }
  }

  static void printXMLSingle(PrintWriter out,
			     AttributeDefinition ad) {
    
    String tag = getXSDType(ad.getType());
    
    String[] optValues = ad.getOptionValues();
    String[] optLabels = ad.getOptionLabels();
    String   desc = ad.getDescription();

    if(optValues != null) {
      out.println("      <xsd:simpleType name = \"" + ad.getID() + "\">");
      out.println("       <xsd:restriction base=\"" + tag + "\">");
      for(int i = 0; i < optValues.length; i++) {
	out.println("       <xsd:enumeration value=\"" + optValues[i] + "\">");
	if(optLabels != null) {
	  printAnnotation(optLabels[i], "        ", out);
	}
	out.println("       </xsd:enumeration>");
      }
      out.println("       </xsd:restriction>");
      out.println("      </xsd:simpleType>");
    } else {
      if("".equals(desc)) {
	out.println("     <xsd:element name=\"" + ad.getID() + "\"" +
		    " type=\"" + tag + "\"/>");
      } else {
	out.println("     <xsd:element name=\"" + ad.getID() + "\"" + 
		    " type=\"" + tag + "\">");
	printAnnotation(desc, "      ", out);
	out.println("     </xsd:element>");
      }
    }
  }

  public static void printValuesXML(List propList,
				    boolean bXMLHeader,
				    PrintWriter out) {

    if(bXMLHeader) {
      out.println("<?xml version=\"1.0\"?>");
    }

    out.println(" <metatype:values\n" + 	"  xmlns:metatype=\"http://www.knopflerfish.org/XMLMetatype\">" );
    out.println("");
    
    for(Iterator it = propList.iterator(); it.hasNext();) {
      Dictionary            props   = (Dictionary)it.next();
      String                pid     = (String)props.get(SERVICE_PID);
      if(pid == null) {
	pid     = (String)props.get("factory.pid");
      }

      out.println("");
      out.println("  <!-- pid " + pid + " -->");
      out.println("  <" + pid + ">");
      printPropertiesXML(props, out);
      out.println("  </" + pid + ">");
    }

    out.println(" </metatype:values>");
  }

  static void printPropertiesXML(Dictionary props, PrintWriter out) {
    for(Enumeration e = props.keys(); e.hasMoreElements(); ) {
      String key = (String)e.nextElement();
      Object val = props.get(key);
      
      if(val instanceof Vector) {
	out.println("   <" + key + ">");
	Vector v = (Vector)val;
	for(int i = 0; i < v.size(); i++) {
	  out.println("    <item>" + v.elementAt(i) + "</item>");
	}
	out.println("   </" + key + ">");
      } else if(val.getClass().isArray()) {
	out.println("   <" + key + ">");
	for(int i = 0; i < Array.getLength(val); i++) {
	  out.println("    <item>" + Array.get(val, i) + "</item>");
	}
	out.println("   </" + key + ">");
      } else {
	out.println("   <" + key + ">" + val.toString() + "</" + key + ">");
      }
    }
  }


  static String getXSDType(int type) {
    String tag = "";
    switch(type) {
    case AttributeDefinition.STRING:     return "xsd:string";
    case AttributeDefinition.INTEGER:    return "xsd:int";   
    case AttributeDefinition.LONG:       return "xsd:long";  
    case AttributeDefinition.SHORT:      return "xsd:short"; 
    case AttributeDefinition.DOUBLE:     return "xsd:double";
    case AttributeDefinition.CHARACTER:  return "xsd:char";
    case AttributeDefinition.FLOAT:      return "xsd:float"; 
    case AttributeDefinition.BOOLEAN:    return "xsd:boolean"; 
    case AttributeDefinition.BIGINTEGER: return "xsd:integer"; 
    case AttributeDefinition.BIGDECIMAL: return "xsd:decimal"; 
    default: throw new IllegalArgumentException("Cannot print " + type);
    }
  }

  static void printAnnotation(String s, String prefix, PrintWriter out) {
    out.println(prefix + "<xsd:annotation>");
    out.println(prefix + " <xsd:documentation>" + s + "</xsd:documentation>");
    out.println(prefix + "</xsd:annotation>");
  }

  static void assertTagName(XMLElement el, 
			    String name) {
    assertTagName(el, null, name);
  }

  static void assertTagName(XMLElement el, 
			    String namespace, 
			    String name) {
    if(!isName(el, namespace, name)) {
      throw new XMLException("Excepted tag '" + namespace + ":" + name + 
			     "', found '" + el.getFullName() + "'", el);
    }
  }


  static boolean isName(XMLElement el, 
			String namespace, 
			String name) {

    boolean b =  el.getName().equals(name) && 
      (namespace == null || 
       el.getNamespace() == null ||
       namespace.equals(el.getNamespace()));
    
    return b;
  }

  private static String[] toStringArray(Object[] val) {
    String[] r = new String[val.length];
    for(int i = 0; i < val.length; i++) {
      r[i] = AD.escape(val[i].toString());
    }

    return r;
  }

  private static String[] toStringArray(Vector val) {
    String[] r = new String[val.size()];
    for(int i = 0; i < val.size(); i++) {
      r[i] = AD.escape(val.elementAt(i).toString());
    }

    return r;
  }
  
  
  
  
  
  //-----------------  R4 ----------------------------------------------------------
  
  //TODO finish the impl
  
  static final String METADATA               = "MetaData";
  static final String OCD                    = "OCD";
  static final String AD_E                   = "AD";
  static final String OBJECT                 = "Object";
  static final String ATTRIBUTE              = "Attribute";
  static final String DESIGNATE              = "Designate";
  static final String OPTION                 = "Option";
  static final String ICON                   = "Icon";
  static final String VALUE                  = "Value";
  static final String CONTENT                = "Content";
  
  static final String ATTR_LOCALIZATION      = "localization";
  
  static final String ATTR_ID                = "id";
  static final String ATTR_DESCRIPTION       = "description";
  
  static final String ATTR_CARDINALITY       = "cardinality";
  static final String ATTR_MIN               = "min";
  static final String ATTR_MAX               = "max";
  static final String ATTR_DEFAULT           = "default";
  static final String ATTR_REQUIRED          = "required";
  
  static final String ATTR_OCDREF            = "ocdref";
  
  static final String ATTR_ADREF             = "adref";
  static final String ATTR_CONTENT           = "content";
  
  static final String ATTR_FACTORYPID        = "factoryPid";
  static final String ATTR_BUNDLE            = "bundle";
  static final String ATTR_OPTIONAL          = "optional";
  static final String ATTR_MERGE             = "merge";
  
  static final String ATTR_LABEL             = "label";
  
  static final String ATTR_RESOURCE          = "resource";
  static final String ATTR_SIZE              = "size";
  
  
  private static final String CHARACTER_ENCODING = "UTF8";
  
  private static XmlPullParser xml_parser/* = null*/;
  
  private static String content/* = null*/;
  
  private static BundleMetaTypeResource currentBMTR;
  private static MetaData currentMetaData;
  private static OCD currentOCD;
  private static AD currentAD;
  private static Vector currentOptionLabels = new Vector();
  private static Vector currentOptionValues = new Vector();
  
  private static String currentDesignatePid;
  private static String currentDesignateFactoryPid;
  private static String currentObjectOCDref;
  
  private static ServiceTracker confAdminTracker;
  private static Configuration currentConf;
  private static Vector currentAttributes;
  private static AE currentAE;
  
  private static Bundle currentBundle;
  
  public static BundleMetaTypeResource loadBMTIfromUrl(BundleContext bc, Bundle b, URL url) throws IOException {
	  InputStream in = null;
	  
	  if(xml_parser == null){
		  xml_parser = new KXmlParser();
		  
		  confAdminTracker = new ServiceTracker(bc, ConfigurationAdmin.class.getName(), null);
		  confAdminTracker.open();  
	  }
	  
	  currentBMTR = new BundleMetaTypeResource(b);
	  
	  currentBundle = b;
	  
	  try {
	      processDocument(xml_parser, url);
	      return currentBMTR;
	  } 
	  catch (Exception e) {
	      throw new IOException("Failed to load " + url + " " + e);
	  } 

  } //method
  
  private static void processDocument(XmlPullParser xpp, URL url) 
  	throws XmlPullParserException, IOException {  
    InputStream in = null;
    try {
      in = url.openStream();

      xpp.setInput(in, CHARACTER_ENCODING);

      int eventType = xpp.getEventType();

      do {
        if(eventType == XmlPullParser.START_DOCUMENT) {
          //   System.out.println("Start document");
        } else if(eventType == XmlPullParser.END_DOCUMENT) {
          //   System.out.println("End document");
        } else if(eventType == XmlPullParser.START_TAG) {
          try{
            startElement(xpp.getName(), url);
          }
          catch(Exception e){
            //System.out.println("Got exception:" + e);
            //e.printStackTrace(System.err);
          }
          //  System.out.println("Start element: " + name);
        } else if(eventType == XmlPullParser.END_TAG) {
          try{
            endElement(xpp.getName(), content);
          }
          catch(Exception e){
            // System.out.println("Got exception");
          }
          //  System.out.println("End element: " + name);
        } else if(eventType == XmlPullParser.TEXT) {   
          
          content = xpp.getText().trim(); 	
          //    System.out.println("Text: " + content);   
        } else{
          //	  System.out.println("Got something else");
        }
        try{
          eventType = xpp.next();
        }
        catch(java.io.IOException ex){
          
          //System.out.println(ex); //stream closed for example
          return; //TODO proper handling
        }
        catch(XmlPullParserException e){ //catch also initial call upstairs
          //System.out.println(e); 
          return; //TODO proper handling
        }
      } while (eventType != XmlPullParser.END_DOCUMENT);
      //  System.out.println("End document");
      //  System.out.flush();
    } finally {
      if (in != null) {
        try {
          in.close();
        } catch (IOException _ignore) { }
      }
    }
  } //method
  
  
  //any missing attribute gets the element ignored
  protected static void startElement(String element, URL sourceURL) throws Exception {
	  int n_attrs = xml_parser.getAttributeCount();
	  HashMap attrs = new HashMap();
	  for(int i = 0; i < n_attrs; i++){
		  attrs.put(xml_parser.getAttributeName(i), xml_parser.getAttributeValue(i));
	  }
	  
	  if (METADATA.equals(element) || element.endsWith(METADATA)) {
		  String localization = (String) attrs.get(ATTR_LOCALIZATION);
	      if(localization != null){
	    	  currentMetaData = new MetaData(localization, currentBundle);
	      }
	      else{
	    	  currentMetaData = new MetaData(currentBundle);
	      }
	  } 
	  else if (OCD.equals(element)) {
		  String id = (String) attrs.get(ATTR_ID);
	      if(id == null){
	    	  return;//TODO not valid: required attribute is missing
	      }
	      
	      String name = (String) attrs.get(ATTR_NAME);
	      if(name == null){
              //TODO not valid: required attribute is missing
	    	  return;
	      }
	      
	      String desc = (String) attrs.get(ATTR_DESCRIPTION);
	      
	      currentOCD = new OCD(id, name, desc, sourceURL);
	  }
	  else if (AD_E.equals(element)) { 
		  String id = (String) attrs.get(ATTR_ID);
	      if(id == null){
              //TODO not valid: required attribute is missing
	    	  return;
	      }
	      
	      String name = (String) attrs.get(ATTR_NAME);
	      String desc = (String) attrs.get(ATTR_DESCRIPTION);
	      
	      String typeS = (String) attrs.get(ATTR_TYPE);
	      int type;
	      if(typeS != null){
	    	  type = getType(typeS);
	      }
	      else{
	    	  //TODO not valid: required attribute is missing
	    	  return;
	      }
	      
	      String card = (String) attrs.get(ATTR_CARDINALITY);
	      int cardinality;
	      if(card != null){
	    	  cardinality = Integer.parseInt(card);
	      }
	      else{
	    	  cardinality = 0;
	      }
	      
	      String min = (String) attrs.get(ATTR_MIN);
	      String max = (String) attrs.get(ATTR_MAX);
	      
	      String default_attr = (String) attrs.get(ATTR_DEFAULT);
	      String[] defaults = null;
	      if(default_attr != null){
	    	  StringTokenizer st = new StringTokenizer(default_attr, ",");
	    	  int number = st.countTokens(); 
	    	  if(number > 0) {
	    		  defaults = new String[number];
	    		  for(int i = 0; i < defaults.length; i++){
	    			  defaults[i] = st.nextToken();
	    		  }
	    	  }
	      }
	      
	      String requiredS = (String) attrs.get(ATTR_REQUIRED);
	      boolean required;
	      if(requiredS != null){
	    	  required = Boolean.valueOf(requiredS).booleanValue();
	      }
	      else{
	    	  required = true;
	      }
	      
	      currentAD = new AD(id, type, cardinality, name, desc, defaults, min, max, required);     
	  }
	  else if (OBJECT.equals(element)) { 
		  String ocdref = (String) attrs.get(ATTR_OCDREF);
	      if(ocdref != null){
	    	  currentObjectOCDref = ocdref;
	      }
	      else{
	    	  //TODO not valid: required attribute is missing
	    	  return;
	      }      
	  }
	  else if (ATTRIBUTE.equals(element)) { 
		  String adref = (String) attrs.get(ATTR_ADREF);
	      if(adref != null){
	    	  currentAE = new AE(adref);
	      }
	      else{
	    	  //TODO not valid: required attribute is missing
	    	  return;
	      }
	      
	      String content = (String) attrs.get(ATTR_CONTENT);
	      if(content != null){
	    	  StringTokenizer st = new StringTokenizer(content, ",");
		      while(st.hasMoreTokens()){
		    	  currentAE.addValue(st.nextToken());
		      }
	      }
	     
	      currentAttributes.add(currentAE);
	  }
	  else if (DESIGNATE.equals(element)) {
		  //SPCES what do we mean by optional exactly?
		  boolean optionalB;
		  String optional = (String) attrs.get(ATTR_OPTIONAL);
	      if(optional != null){
	    	  optionalB = Boolean.valueOf(optional).booleanValue();
	      }
	      else{
	    	  optionalB = false;
	      }
	      
		  String pid = (String) attrs.get(ATTR_PID);
	      if(pid != null){
	    	  currentDesignatePid = pid;
	      }
	      else{
	    	  //TODO not valid: required attribute is missing
	    	  if(!optionalB){
	    		  return;
	    	  }
	      }
	      
	      String factoryPid = (String) attrs.get(ATTR_FACTORYPID);
	      if(factoryPid != null && !factoryPid.equals("")){
	    	  currentDesignateFactoryPid = factoryPid;
	    	  currentDesignatePid = null;
	      }
	       
	      String bundle_location = (String) attrs.get(ATTR_BUNDLE);
	     
	      if(currentDesignatePid != null){
	    	  ConfigurationAdmin ca = (ConfigurationAdmin) confAdminTracker.getService();
	    	  if(ca != null){
	    		  currentConf = ca.getConfiguration(currentDesignatePid, bundle_location);
	      
	    		  String merge = (String) attrs.get(ATTR_MERGE);
	    		  
	    		  if(merge == null || !Boolean.valueOf(merge).booleanValue()){
	    				  currentConf.delete();
	    				  currentConf = ca.getConfiguration(currentDesignatePid, bundle_location);
	    		  }
	    		  
	    		  String location = currentConf.getBundleLocation();
	    		  if(location != null && !location.equals(bundle_location)){
	    			  //currentConf = null; //will prevent processing
	    			  currentConf.setBundleLocation(bundle_location);
	    		  } 
	    	  }
	      }
	      else if (currentDesignateFactoryPid != null){
	    	  ConfigurationAdmin ca = (ConfigurationAdmin) confAdminTracker.getService();
	    	  if(ca != null){
	    		  currentConf = ca.createFactoryConfiguration(currentDesignateFactoryPid, bundle_location);
	    		  //merge is meaningless
	    	  }
	      }
	      
	      currentAttributes = new Vector();
	  }
	  else if (OPTION.equals(element)) {
		  
		  String label = (String) attrs.get(ATTR_LABEL);
	      if(label != null){
	    	  currentOptionLabels.add(label);
	      }
	      else{
	    	  //TODO not valid: required attribute is missing
	    	  return;
	      }
	      
	    
	      String value = (String) attrs.get(ATTR_VALUE);
	      if(value != null){
	    	  currentOptionValues.add(value);
	      }
	      else{
	    	  //TODO not valid: required attribute is missing
	    	  return;
	      }
	      
	  }
          else if (ICON.equals(element)) {
		  
		  String resource = (String) attrs.get(ATTR_RESOURCE);
	      if(resource == null){
              //TODO not valid: required attribute is missing
	    	  return;
	      }
	      
	      String sizeS = (String) attrs.get(ATTR_SIZE);
	      int size;
	      if(sizeS != null){
	    	  size = Integer.parseInt(sizeS);
	      }
	      else{
	    	  //TODO not valid: required attribute is missing
	    	  return;
	      }
	      
	      currentOCD.addIcon(size, resource);
	  }  
	  
  }
  
  
  
  
  protected static void endElement(String element, String content) throws Exception {
      
	  if (METADATA.equals(element) || element.endsWith(METADATA)) {
		  currentBMTR.addMetaData(currentMetaData);
		  currentMetaData.prepare();
		  currentMetaData = null;
	  } 
	  else if (OCD.equals(element)) {
		  currentMetaData.addOCD(currentOCD);
		  currentOCD = null;
	  }
	  else if (AD_E.equals(element)) {
		  currentOCD.add(currentAD, currentAD.getRequired());
		  String[] optionValues = null;
		  String[] optionLabels = null;
		  int number;
		  if((number = currentOptionValues.size()) > 0){
			  optionValues = (String[]) currentOptionValues.toArray(new String[number]);
			  //same number
			  optionLabels = (String[]) currentOptionLabels.toArray(new String[number]);
		  }
		  currentAD.setOptions(optionValues, optionLabels);
		  currentOptionValues.removeAllElements();
		  currentOptionLabels.removeAllElements();
		  currentAD = null;
	  }
	  else if (DESIGNATE.equals(element)) {
		  //MetaInfo
		  currentMetaData.designate(currentDesignateFactoryPid, currentDesignatePid,
				                    currentObjectOCDref, currentConf, currentAttributes);
	      
		  currentDesignatePid = null;
		  currentDesignateFactoryPid = null;
		  currentAttributes = null;
		  currentObjectOCDref = null;
		  currentConf = null;
	  } //seems like not sure yet see: http://membercvs.osgi.org/bugs/show_bug.cgi?id=129
	  else if (VALUE.equals(element) || CONTENT.equals(element)) {
		  currentAE.addValue(content);
	  }  
  }
  
  static int getType(String strType) {
	    int type = -1;

	    if("Integer".equals(strType)) {
	      type = AttributeDefinition.INTEGER;
	    } 
	    else if("String".equals(strType)) {
	      type = AttributeDefinition.STRING;
	    } 
	    else if("Boolean".equals(strType)) {
	      type = AttributeDefinition.BOOLEAN;
	    } 
	    else if("Float".equals(strType)) {
	      type = AttributeDefinition.FLOAT;
	    } 
	    else if("Long".equals(strType)) {
	      type = AttributeDefinition.LONG;
	    } 
	    else if("Short".equals(strType)) {
	      type = AttributeDefinition.SHORT;
	    } 
	    else if("Char".equals(strType)) {
	      type = AttributeDefinition.CHARACTER;
	    } 
	    else if("Double".equals(strType)) {
	      type = AttributeDefinition.DOUBLE;
	    } 
	    else {
	      throw new IllegalArgumentException("Unsupported type '" + strType);
	    }

	    return type;
  }

} //class

class XMLException extends IllegalArgumentException {
  XMLElement el;

  private XMLException() {
  }

  public XMLException(XMLElement el) {
    this("", el);
  }

  public XMLException(String msg, XMLElement el) {
    super(msg + ", line=" + el.getLineNr());
    this.el = el;
  }

  public XMLElement getXMLElement() {
    return el;
  }
}


class CMConfig {
  public String  pid;
  public int     maxInstances = 1;
  public AD[]    ads;
  public String  desc;
  public String  iconURL;

  public CMConfig(String pid,
		  AD[] ads, 
		  String desc,
		  String iconURL,
		  int maxInstances) {
    this.pid          = pid;
    this.ads          = ads;
    this.desc         = desc != null ? desc : "";
    this.iconURL      = iconURL;
    this.maxInstances = maxInstances;
  }

  public String toString() {
    StringBuffer sb = new StringBuffer();

    sb.append("CMConfig[");
    sb.append("pid=" + pid);
    sb.append(", desc=" + desc);
    sb.append(", iconURL=" + iconURL);
    sb.append(", maxInstances=" + maxInstances);
    sb.append(", attribs=");
    for(int i = 0; i < ads.length; i++) {
      sb.append(ads[i]);
      if(i < ads.length - 1) {
	sb.append(", ");
      }
    }
    
    return sb.toString();
  }
}

class Annotation {
  String appinfo;
  String doc;

  public Annotation() {
  }
}

