Document parseDocument(String filename) throws Exception {
   FileReader reader = new FileReader(filename);
   String firstLine = new BufferedReader(reader).readLine();
   reader.close();
   Document document = null;
   if (firstLine.startsWith("<?xml")) {
     System.err.println("XML detected; using default XML parser.");
   } else {
     try {
       Class nekoParserClass = Class.forName("org.cyberneko.html.parsers.DOMParser");
       Object parser = nekoParserClass.newInstance();
       Method parse = nekoParserClass.getMethod("parse", new Class[] {String.class});
       Method getDocument = nekoParserClass.getMethod("getDocument", new Class[0]);
       parse.invoke(parser, filename);
       document = (Document) getDocument.invoke(parser);
     } catch (Exception e) {
       System.err.println("NekoHTML HTML parser not found; HTML4 support disabled.");
     }
   }
   if (document == null) {
     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
     try { // http://www.w3.org/blog/systeam/2008/02/08/w3c_s_excessive_dtd_traffic
       factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
     } catch (ParserConfigurationException e) {
       System.err.println("Warning: Could not disable external DTD loading");
     }
     DocumentBuilder builder = factory.newDocumentBuilder();
     document = builder.parse(filename);
   }
   return document;
 }
 /**
  * @author Neil Rotstan
  * @since 1.0
  */
 static {
   try {
     m_documentCreator = DocumentBuilderFactory.newInstance().newDocumentBuilder();
   } catch (ParserConfigurationException parserError) {
     throw new RuntimeException(
         "XMLManager: Unable to setup DocumentBuilder: " + parserError.getMessage());
   }
 }
Пример #3
0
/**
 * Performs the actual reading of an XML document and copies the values into a bean. This class uses
 * the DocumentBuilder portion of the Java XML API. It is not as efficient as SAX, but much easier
 * to deal with when copying values into beans.
 *
 * @author Mark Wutka
 * @version 1.0 05/08/2000
 * @version 1.1 05/09/2000
 * @version 1.6 07/30/2000
 */
class JOXBeanInput {
  /** The document builder factory used to instantiate new document builders. */
  protected static DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

  protected static Hashtable beanCache = new Hashtable();

  /**
   * Reads an XML document from an input source and copies its values into the specified object
   *
   * @param ob The object to receive the values
   * @param source The location of the XML document
   * @throws IOException If there is an error reading the document
   */
  public void readObject(Object ob, InputSource source) throws IOException {
    try {
      // Create a document builder to read the document
      DocumentBuilder builder = factory.newDocumentBuilder();

      // Read the document
      Document doc = builder.parse(source);

      // Get the root element
      Element element = doc.getDocumentElement();

      // Copy the root element into the bean
      readObject(ob, element);
    } catch (SAXException exc) {
      throw new IOException("Error parsing XML document: " + exc.toString());
    } catch (ParserConfigurationException exc) {
      throw new IOException("Error parsing XML document: " + exc.toString());
    }
  }

  /**
   * Reads the children of an XML element and matches them to properties of a bean.
   *
   * @param ob The bean to receive the values
   * @param element The element the corresponds to the bean
   * @throws IOException If there is an error reading the document
   */
  public void readObject(Object ob, Element element) throws IOException {
    // If the object is null, skip the element
    if (ob == null) {
      return;
    }

    try {
      BeanInfo info = (BeanInfo) beanCache.get(ob.getClass());

      if (info == null) {
        // Get the bean info for the object
        info = Introspector.getBeanInfo(ob.getClass(), Object.class);

        beanCache.put(ob.getClass(), info);
      }

      // Get the object's properties
      PropertyDescriptor[] props = info.getPropertyDescriptors();

      // Get the attributes of the node
      NamedNodeMap attrs = element.getAttributes();

      // Get the children of the XML element
      NodeList nodes = element.getChildNodes();

      int numNodes = nodes.getLength();

      for (int i = 0; i < props.length; i++) {
        // Treat indexed properties a little differently
        if (props[i] instanceof IndexedPropertyDescriptor) {
          readIndexedProperty(ob, (IndexedPropertyDescriptor) props[i], nodes, attrs);
        } else {
          readProperty(ob, props[i], nodes, attrs);
        }
      }
    } catch (IntrospectionException exc) {
      throw new IOException(
          "Error getting bean info for " + ob.getClass().getName() + ": " + exc.toString());
    }
  }

  /**
   * Reads an XML element into a bean property by first locating the XML element corresponding to
   * this property.
   *
   * @param ob The bean whose property is being set
   * @param desc The property that will be set
   * @param nodes The list of XML items that may contain the property
   * @throws IOException If there is an error reading the document
   */
  public void readProperty(Object ob, PropertyDescriptor desc, NodeList nodes, NamedNodeMap attrs)
      throws IOException {
    int numAttrs = attrs.getLength();

    for (int i = 0; i < numAttrs; i++) {
      // See if the attribute name matches the property name
      if (namesMatch(desc.getName(), attrs.item(i).getNodeName())) {
        // Get the method used to set this property
        Method setter = desc.getWriteMethod();

        // If this object has no setter, don't bother writing it
        if (setter == null) continue;

        // Get the value of the property
        Object obValue = getObjectValue(desc, attrs.item(i).getNodeValue());
        if (obValue != null) {
          try {
            // Set the property value
            setter.invoke(ob, new Object[] {obValue});
          } catch (InvocationTargetException exc) {
            throw new IOException(
                "Error setting property " + desc.getName() + ": " + exc.toString());
          } catch (IllegalAccessException exc) {
            throw new IOException(
                "Error setting property " + desc.getName() + ": " + exc.toString());
          }
        }

        return;
      }
    }

    int numNodes = nodes.getLength();

    Vector arrayBuild = null;

    for (int i = 0; i < numNodes; i++) {
      Node node = nodes.item(i);

      // If this node isn't an element, skip it
      if (!(node instanceof Element)) continue;

      Element element = (Element) node;

      // See if the tag name matches the property name
      if (namesMatch(desc.getName(), element.getTagName())) {
        // Get the method used to set this property
        Method setter = desc.getWriteMethod();

        // If this object has no setter, don't bother writing it
        if (setter == null) continue;

        // Get the value of the property
        Object obValue = getObjectValue(desc, element);

        // 070201 MAW: Modified from change submitted by Steve Poulson
        if (setter.getParameterTypes()[0].isArray()) {
          if (arrayBuild == null) {
            arrayBuild = new Vector();
          }
          arrayBuild.addElement(obValue);

          // 070201 MAW: Go ahead and read through the rest of the nodes in case
          //             another one matches the array. This has the effect of skipping
          //             over the "return" statement down below
          continue;
        }

        if (obValue != null) {
          try {
            // Set the property value
            setter.invoke(ob, new Object[] {obValue});
          } catch (InvocationTargetException exc) {
            throw new IOException(
                "Error setting property " + desc.getName() + ": " + exc.toString());
          } catch (IllegalAccessException exc) {
            throw new IOException(
                "Error setting property " + desc.getName() + ": " + exc.toString());
          }
        }
        return;
      }
    }

    // If we build a vector of array members, convert the vector into
    // an array and save it in the property
    if (arrayBuild != null) {
      // Get the method used to set this property
      Method setter = desc.getWriteMethod();

      if (setter == null) return;

      Object[] obValues = (Object[]) Array.newInstance(desc.getPropertyType(), arrayBuild.size());

      arrayBuild.copyInto(obValues);

      try {
        setter.invoke(ob, new Object[] {obValues});
      } catch (InvocationTargetException exc) {
        throw new IOException("Error setting property " + desc.getName() + ": " + exc.toString());
      } catch (IllegalAccessException exc) {
        throw new IOException("Error setting property " + desc.getName() + ": " + exc.toString());
      }

      return;
    }
  }

  /**
   * Reads XML element(s) into an indexed bean property by first locating the XML element(s)
   * corresponding to this property.
   *
   * @param ob The bean whose property is being set
   * @param desc The property that will be set
   * @param nodes The list of XML items that may contain the property
   * @throws IOException If there is an error reading the document
   */
  public void readIndexedProperty(
      Object ob, IndexedPropertyDescriptor desc, NodeList nodes, NamedNodeMap attrs)
      throws IOException {
    // Create a vector to hold the property values
    Vector v = new Vector();

    int numAttrs = attrs.getLength();

    for (int i = 0; i < numAttrs; i++) {
      // See if this attribute matches the property name
      if (namesMatch(desc.getName(), attrs.item(i).getNodeName())) {
        // Get the property value
        Object obValue = getObjectValue(desc, attrs.item(i).getNodeValue());

        if (obValue != null) {
          // Add the value to the list of values to be set
          v.addElement(obValue);
        }
      }
    }

    int numNodes = nodes.getLength();

    for (int i = 0; i < numNodes; i++) {
      Node node = nodes.item(i);

      // Skip non-element nodes
      if (!(node instanceof Element)) continue;

      Element element = (Element) node;

      // See if this element tag matches the property name
      if (namesMatch(desc.getName(), element.getTagName())) {
        // Get the property value
        Object obValue = getObjectValue(desc, element);

        if (obValue != null) {
          // Add the value to the list of values to be set
          v.addElement(obValue);
        }
      }
    }

    // Get the method used to set the property value
    Method setter = desc.getWriteMethod();

    // If this property has no setter, don't write it
    if (setter == null) return;

    // Create a new array of property values
    Object propArray = Array.newInstance(desc.getPropertyType().getComponentType(), v.size());

    // Copy the vector into the array
    v.copyInto((Object[]) propArray);

    try {
      // Store the array of property values
      setter.invoke(ob, new Object[] {propArray});
    } catch (InvocationTargetException exc) {
      throw new IOException("Error setting property " + desc.getName() + ": " + exc.toString());
    } catch (IllegalAccessException exc) {
      throw new IOException("Error setting property " + desc.getName() + ": " + exc.toString());
    }
  }

  /**
   * Examines a property's type to see which method should be used to parse the property's value.
   *
   * @param desc The description of the property
   * @param element The XML element containing the property value
   * @return The value stored in the element
   * @throws IOException If there is an error reading the document
   */
  public Object getObjectValue(PropertyDescriptor desc, Element element) throws IOException {
    // Find out what kind of property it is
    Class type = desc.getPropertyType();

    // If it's an array, get the base type
    if (type.isArray()) {
      type = type.getComponentType();
    }

    // For native types, object wrappers for natives, and strings, use the
    // basic parse routine
    if (type.equals(Integer.TYPE)
        || type.equals(Long.TYPE)
        || type.equals(Short.TYPE)
        || type.equals(Byte.TYPE)
        || type.equals(Boolean.TYPE)
        || type.equals(Float.TYPE)
        || type.equals(Double.TYPE)
        || Integer.class.isAssignableFrom(type)
        || Long.class.isAssignableFrom(type)
        || Short.class.isAssignableFrom(type)
        || Byte.class.isAssignableFrom(type)
        || Boolean.class.isAssignableFrom(type)
        || Float.class.isAssignableFrom(type)
        || Double.class.isAssignableFrom(type)
        || String.class.isAssignableFrom(type)) {
      return readBasicType(type, element);
    } else if (java.util.Date.class.isAssignableFrom(type)) {
      // If it's a date, use the date parser
      return readDate(element);
    } else {
      try {
        // If it's an object, create a new instance of the object (it should
        // be a bean, or there will be trouble)
        Object newOb = type.newInstance();

        // Copy the XML element into the bean
        readObject(newOb, element);

        return newOb;
      } catch (InstantiationException exc) {
        throw new IOException(
            "Error creating object for " + desc.getName() + ": " + exc.toString());
      } catch (IllegalAccessException exc) {
        throw new IOException(
            "Error creating object for " + desc.getName() + ": " + exc.toString());
      }
    }
  }

  /**
   * Examines a property's type to see which method should be used to parse the property's value.
   *
   * @param desc The description of the property
   * @param value The value of the XML attribute containing the prop value
   * @return The value stored in the element
   * @throws IOException If there is an error reading the document
   */
  public Object getObjectValue(PropertyDescriptor desc, String value) throws IOException {
    // Find out what kind of property it is
    Class type = desc.getPropertyType();

    // If it's an array, get the base type
    if (type.isArray()) {
      type = type.getComponentType();
    }

    // For native types, object wrappers for natives, and strings, use the
    // basic parse routine
    if (type.equals(Integer.TYPE)
        || type.equals(Long.TYPE)
        || type.equals(Short.TYPE)
        || type.equals(Byte.TYPE)
        || type.equals(Boolean.TYPE)
        || type.equals(Float.TYPE)
        || type.equals(Double.TYPE)
        || Integer.class.isAssignableFrom(type)
        || Long.class.isAssignableFrom(type)
        || Short.class.isAssignableFrom(type)
        || Byte.class.isAssignableFrom(type)
        || Boolean.class.isAssignableFrom(type)
        || Float.class.isAssignableFrom(type)
        || Double.class.isAssignableFrom(type)) {
      return parseBasicType(type, value);
    } else if (String.class.isAssignableFrom(type)) {
      return value;
    } else if (java.util.Date.class.isAssignableFrom(type)) {
      // If it's a date, use the date parser
      return parseDate(value, JOXDateHandler.determineDateFormat());
    } else {
      return null;
    }
  }

  /**
   * Reads an XML text element into a basic type
   *
   * @param type The type of the element to read
   * @param element The element containing the value
   * @return The parsed value of the element
   */
  public static Object readBasicType(Class type, Element element) {
    // Get the text corresponding to this element
    String str = getElementString(element);

    // If there isn't any, don't parse
    if (str == null) return null;

    // If it's a string, don't need to do anything else
    if (String.class.isAssignableFrom(type)) return str;

    return parseBasicType(type, str);
  }

  /**
   * Reads an string into a basic type
   *
   * @param type The type of the string to read
   * @param str The string containing the value
   * @return The parsed value of the string
   */
  public static Object parseBasicType(Class type, String str) {
    // Parse the text based on the property type

    if (type.equals(Integer.TYPE) || Integer.class.isAssignableFrom(type)) {
      return new Integer(str);
    } else if (type.equals(Long.TYPE) || Long.class.isAssignableFrom(type)) {
      return new Long(str);
    } else if (type.equals(Short.TYPE) || Short.class.isAssignableFrom(type)) {
      return new Short(str);
    } else if (type.equals(Byte.TYPE) || Byte.class.isAssignableFrom(type)) {
      return new Byte(str);
    } else if (type.equals(Boolean.TYPE) || Boolean.class.isAssignableFrom(type)) {
      return new Boolean(str);
    } else if (type.equals(Float.TYPE) || Float.class.isAssignableFrom(type)) {
      return new Float(str);
    } else if (type.equals(Double.TYPE) || Double.class.isAssignableFrom(type)) {
      return new Double(str);
    }

    return null;
  }

  /**
   * Parses an XML element as a date
   *
   * @param element The element containing the date
   * @return The date value parsed from the element
   * @throws IOException If there's an error parsing the date
   */
  public Object readDate(Element element) throws IOException {
    // Get the text corresponding to this element
    String str = getElementString(element);
    String fmt = element.getAttribute("format");
    if ("".equals(fmt)) {
      return parseDate(str, JOXDateHandler.determineDateFormat());
    } else {
      return parseDate(str, new SimpleDateFormat(fmt));
    }
  }

  /**
   * Parses a string as a date
   *
   * @param str The string containing the date
   * @return The date value parsed from the string
   * @throws IOException If there's an error parsing the date
   */
  public Object parseDate(String str, DateFormat dateFormat) throws IOException {
    // If there isn't any, don't parse
    if (str == null) return null;

    try {
      return dateFormat.parse(str);
    } catch (ParseException exc) {
      throw new IOException("Error parsing date " + str);
    }
  }

  /**
   * Searches the children of an element looking for a Text node. If it finds one, it returns it.
   *
   * @param element The element whose children will be searched
   * @return The text for the element, or null if there is none
   */
  public static String getElementString(Element element) {
    NodeList nodes = element.getChildNodes();

    int numNodes = nodes.getLength();

    for (int i = 0; i < numNodes; i++) {
      Node node = nodes.item(i);

      if (node instanceof Text) {
        return ((Text) node).getData();
      }
    }

    return null;
  }

  /**
   * Returns true if two names match without regard to case or the presence of '-' or '_'
   * characters.
   *
   * @param beanName The name of the bean property to compare
   * @param elementName The name of the element to compare
   * @return True if the names match
   */
  public static boolean namesMatch(String beanName, String elementName) {
    int beanNameLen = beanName.length();
    int elementNameLen = elementName.length();

    int elementPos = 0;
    int beanPos = 0;

    // Keep looping until you hit the end of either of the strings
    while ((elementPos < elementNameLen) && (beanPos < beanNameLen)) {
      // If the next character in the bean name is a '-' or a '/', skip it
      char beanCh = Character.toLowerCase(beanName.charAt(beanPos));
      if ((beanCh == '-') || (beanCh == '_')) {
        beanPos++;
        continue;
      }

      // If the next character in the element name is a '-' or a '/', skip it
      char elementCh = Character.toLowerCase(elementName.charAt(elementPos));
      if ((elementCh == '-') || (elementCh == '_')) {
        elementPos++;
        continue;
      }

      // If the characters don't match, the names don't match
      if (elementCh != beanCh) return false;
      elementPos++;
      beanPos++;
    }

    // You hit the end of both names at the same time, the names match
    if ((elementPos == elementNameLen) && (beanPos == beanNameLen)) {
      return true;
    }

    return false;
  }
}
Пример #4
0
public class MetatypeTest extends TestCase {
  static DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  static XPathFactory xpathf = XPathFactory.newInstance();
  static XPath xpath = xpathf.newXPath();

  static DocumentBuilder db;

  static {
    try {
      dbf.setNamespaceAware(true);
      db = dbf.newDocumentBuilder();
      xpath.setNamespaceContext(
          new NamespaceContext() {

            @Override
            public Iterator<String> getPrefixes(String namespaceURI) {
              return Arrays.asList("md").iterator();
            }

            @Override
            public String getPrefix(String namespaceURI) {
              return "md";
            }

            @Override
            public String getNamespaceURI(String prefix) {
              return "http://www.osgi.org/xmlns/metatype/v1.1.0";
            }
          });
    } catch (ParserConfigurationException e) {
      e.printStackTrace();
      throw new ExceptionInInitializerError(e);
    }
  }

  public static void testOptions() {}

  /**
   * Configuration should return null for nonprimitive properties if not defined. Now it returns 0.
   * Testcase: @OCD interface Config {
   *
   * @ad(required = false) Integer port(); } Config config =
   *     Configurable.createConfigurable(Config.class, properties); assert config.port() == null; //
   *     property port is not set Fix: Please delete "||
   *     Number.class.isAssignableFrom(method.getReturnType())" from
   *     aQute/bnd/annotation/metatype/Configurable.java
   */
  @Meta.OCD
  static interface C {
    @Meta.AD(required = false)
    Integer port();
  }

  public static void testConfigurableForNonPrimitives() {
    Map<String, String> p = new HashMap<String, String>();
    C config = Configurable.createConfigurable(C.class, p);
    assertNull(config.port());
    p.put("port", "10");
    config = Configurable.createConfigurable(C.class, p);
    assertEquals(Integer.valueOf(10), config.port()); // property port is
    // not set
  }

  /** Test method naming options with '.' and reserved names */
  @Meta.OCD
  public static interface Naming {
    String secret();

    String _secret(); // .secret

    String __secret(); // _secret

    String $new(); // new

    String $$new(); // $new

    String a_b_c(); // a.b.c

    String a__b__c(); // a_b_c

    String _a__b(); // .a_b

    String $$$$$$$$a__b(); // $$$$a_b

    String $$$$$$$$a_b(); // $$$$a.b

    String a$(); // a

    String a$$(); // a$

    String a$$$(); // a$

    String a$$$$(); // a$$

    String a$$_$$(); // a$.$

    String a$$__$$(); // a$_$

    String a_$_(); // a..

    @Meta.AD(id = "secret")
    String xsecret();

    @Meta.AD(id = ".secret")
    String x_secret();

    @Meta.AD(id = "_secret")
    String x__secret(); // _secret

    @Meta.AD(id = "new")
    String x$new(); // new

    @Meta.AD(id = "$new")
    String x$$new(); // $new

    @Meta.AD(id = "a.b.c")
    String xa_b_c(); // a.b.c

    @Meta.AD(id = "a_b_c")
    String xa__b__c(); // a_b_c

    @Meta.AD(id = ".a_b")
    String x_a__b(); // .a_b

    @Meta.AD(id = "$$$$a_b")
    String x$$$$$$$$a__b(); // $$$$a_b

    @Meta.AD(id = "$$$$a.b")
    String x$$$$$$$$a_b(); // $$$$a.b

    @Meta.AD(id = "a")
    String xa$(); // a

    @Meta.AD(id = "a$")
    String xa$$(); // a$

    @Meta.AD(id = "a$")
    String xa$$$(); // a$

    @Meta.AD(id = "a$$")
    String xa$$$$(); // a$$

    @Meta.AD(id = "a$.$")
    String xa$$_$$(); // a$.$

    @Meta.AD(id = "a$_$")
    String xa$$__$$(); // a$_$

    @Meta.AD(id = "a..")
    String xa_$_(); // a..

    String noid();

    @Meta.AD(id = Meta.NULL)
    String nullid();
  }

  public static void testNaming() throws Exception {
    Map<String, Object> map = Create.map();

    map.put("_secret", "_secret");
    map.put("_secret", "_secret");
    map.put(".secret", ".secret");
    map.put("$new", "$new");
    map.put("new", "new");
    map.put("secret", "secret");
    map.put("a_b_c", "a_b_c");
    map.put("a.b.c", "a.b.c");
    map.put(".a_b", ".a_b");
    map.put("$$$$a_b", "$$$$a_b");
    map.put("$$$$a.b", "$$$$a.b");
    map.put("a", "a");
    map.put("a$", "a$");
    map.put("a$$", "a$$");
    map.put("a$.$", "a$.$");
    map.put("a$_$", "a$_$");
    map.put("a..", "a..");
    map.put("noid", "noid");
    map.put("nullid", "nullid");

    Naming trt = Configurable.createConfigurable(Naming.class, map);

    // By name
    assertEquals("secret", trt.secret());
    assertEquals("_secret", trt.__secret());
    assertEquals(".secret", trt._secret());
    assertEquals("new", trt.$new());
    assertEquals("$new", trt.$$new());
    assertEquals("a.b.c", trt.a_b_c());
    assertEquals("a_b_c", trt.a__b__c());
    assertEquals(".a_b", trt._a__b());
    assertEquals("$$$$a.b", trt.$$$$$$$$a_b());
    assertEquals("$$$$a_b", trt.$$$$$$$$a__b());
    assertEquals("a", trt.a$());
    assertEquals("a$", trt.a$$());
    assertEquals("a$", trt.a$$$());
    assertEquals("a$.$", trt.a$$_$$());
    assertEquals("a$_$", trt.a$$__$$());
    assertEquals("a..", trt.a_$_());
    assertEquals("noid", trt.noid());
    assertEquals("nullid", trt.nullid());

    // By AD
    assertEquals("secret", trt.xsecret());
    assertEquals("_secret", trt.x__secret());
    assertEquals(".secret", trt.x_secret());
    assertEquals("new", trt.x$new());
    assertEquals("$new", trt.x$$new());
    assertEquals("a.b.c", trt.xa_b_c());
    assertEquals("a_b_c", trt.xa__b__c());
    assertEquals(".a_b", trt.x_a__b());
    assertEquals("$$$$a.b", trt.x$$$$$$$$a_b());
    assertEquals("$$$$a_b", trt.x$$$$$$$$a__b());
    assertEquals("a", trt.xa$());
    assertEquals("a$", trt.xa$$());
    assertEquals("a$", trt.xa$$$());
    assertEquals("a$.$", trt.xa$$_$$());

    Builder b = new Builder();
    b.addClasspath(new File("bin"));
    b.setProperty("Export-Package", "test.metatype");
    b.setProperty("-metatype", "*");
    b.build();
    assertEquals(0, b.getErrors().size());
    assertEquals(0, b.getWarnings().size());

    Resource r = b.getJar().getResource("OSGI-INF/metatype/test.metatype.MetatypeTest$Naming.xml");
    IO.copy(r.openInputStream(), System.err);
    Document d = db.parse(r.openInputStream(), "UTF-8");
    assertEquals(
        "http://www.osgi.org/xmlns/metatype/v1.1.0", d.getDocumentElement().getNamespaceURI());
  }

  /** Test the special conversions. */
  public static class MyList<T> extends ArrayList<T> {
    private static final long serialVersionUID = 1L;

    public MyList() {
      System.err.println("Constr");
    }
  }

  static interface CollectionsTest {
    Collection<String> collection();

    List<String> list();

    Set<String> set();

    Queue<String> queue();

    // Deque<String> deque();
    Stack<String> stack();

    ArrayList<String> arrayList();

    LinkedList<String> linkedList();

    LinkedHashSet<String> linkedHashSet();

    MyList<String> myList();
  }

  public static void testCollections() throws Exception {
    CollectionsTest trt = set(CollectionsTest.class, new int[] {1, 2, 3});
    List<String> source = Arrays.asList("1", "2", "3");

    assertTrue(trt.collection() instanceof Collection);
    assertEqualList(source, trt.collection());

    assertTrue(trt.list() instanceof List);
    assertEqualList(source, trt.list());
    assertTrue(trt.set() instanceof Set);
    assertEqualList(source, trt.set());
    assertTrue(trt.queue() instanceof Queue);
    assertEqualList(source, trt.queue());
    // assertTrue( trt.deque() instanceof Deque);
    // assertEqualList( source, trt.deque());
    assertTrue(trt.stack() instanceof Stack);
    assertEqualList(source, trt.stack());
    assertTrue(trt.arrayList() instanceof ArrayList);
    assertEqualList(source, trt.arrayList());
    assertTrue(trt.linkedList() instanceof LinkedList);
    assertEqualList(source, trt.linkedList());
    assertTrue(trt.linkedHashSet() instanceof LinkedHashSet);
    assertEqualList(source, trt.linkedHashSet());
    assertTrue(trt.myList() instanceof MyList);
    assertEqualList(source, trt.myList());
  }

  private static void assertEqualList(List<?> a, Collection<?> b) {
    if (a.size() == b.size()) {
      for (Object x : a) {
        if (!b.contains(x))
          throw new AssertionFailedError("expected:<" + a + "> but was: <" + b + ">");
      }
      return;
    }
    throw new AssertionFailedError("expected:<" + a + "> but was: <" + b + ">");
  }

  /** Test the special conversions. */
  static interface SpecialConversions {
    enum X {
      A,
      B,
      C
    }

    X enumv();

    Pattern pattern();

    Class<?> clazz();

    URI constructor();
  }

  public static void testSpecialConversions() throws URISyntaxException {
    Properties p = new Properties();
    p.put("enumv", "A");
    p.put("pattern", ".*");
    p.put("clazz", "java.lang.Object");
    p.put("constructor", "http://www.aQute.biz");

    SpecialConversions trt =
        Configurable.createConfigurable(SpecialConversions.class, (Map<Object, Object>) p);
    assertEquals(SpecialConversions.X.A, trt.enumv());
    assertEquals(".*", trt.pattern().pattern());
    assertEquals(Object.class, trt.clazz());
    assertEquals(new URI("http://www.aQute.biz"), trt.constructor());
  }

  /**
   * Test the converter.
   *
   * @throws URISyntaxException
   */
  public static void testConverter() throws URISyntaxException {
    {
      // Test collections as value
      TestReturnTypes trt = set(TestReturnTypes.class, Arrays.asList(55));
      assertTrue(Arrays.equals(new boolean[] {true}, trt.rpaBoolean()));
      assertTrue(Arrays.equals(new byte[] {55}, trt.rpaByte()));
      assertTrue(Arrays.equals(new short[] {55}, trt.rpaShort()));
      assertTrue(Arrays.equals(new int[] {55}, trt.rpaInt()));
      assertTrue(Arrays.equals(new long[] {55}, trt.rpaLong()));
      assertTrue(Arrays.equals(new float[] {55}, trt.rpaFloat()));
      assertTrue(Arrays.equals(new double[] {55}, trt.rpaDouble()));
      assertEquals(Arrays.asList(true), trt.rBooleans());
      assertEquals(Arrays.asList(new Byte((byte) 55)), trt.rBytes());
      assertEquals(Arrays.asList(new Short((short) 55)), trt.rShorts());
      assertEquals(Arrays.asList(Integer.valueOf(55)), trt.rInts());
      assertEquals(Arrays.asList(new Long(55L)), trt.rLongs());
      assertEquals(Arrays.asList(new Float(55F)), trt.rFloats());
      assertEquals(Arrays.asList(new Double(55D)), trt.rDoubles());
      assertEquals(Arrays.asList("55"), trt.rStrings());
      assertEquals(Arrays.asList(new URI("55")), trt.rURIs());

      assertTrue(Arrays.equals(new Boolean[] {true}, trt.raBoolean()));
      assertTrue(Arrays.equals(new Byte[] {55}, trt.raByte()));
      assertTrue(Arrays.equals(new Short[] {55}, trt.raShort()));
      assertTrue(Arrays.equals(new Integer[] {55}, trt.raInt()));
      assertTrue(Arrays.equals(new Long[] {55L}, trt.raLong()));
      assertTrue(Arrays.equals(new Float[] {55F}, trt.raFloat()));
      assertTrue(Arrays.equals(new Double[] {55D}, trt.raDouble()));
      assertTrue(Arrays.equals(new String[] {"55"}, trt.raString()));
      assertTrue(Arrays.equals(new URI[] {new URI("55")}, trt.raURI()));
    }
    {
      // Test primitive arrays as value
      TestReturnTypes trt = set(TestReturnTypes.class, new int[] {55});
      assertTrue(Arrays.equals(new boolean[] {true}, trt.rpaBoolean()));
      assertTrue(Arrays.equals(new byte[] {55}, trt.rpaByte()));
      assertTrue(Arrays.equals(new short[] {55}, trt.rpaShort()));
      assertTrue(Arrays.equals(new int[] {55}, trt.rpaInt()));
      assertTrue(Arrays.equals(new long[] {55}, trt.rpaLong()));
      assertTrue(Arrays.equals(new float[] {55}, trt.rpaFloat()));
      assertTrue(Arrays.equals(new double[] {55}, trt.rpaDouble()));
      assertEquals(Arrays.asList(true), trt.rBooleans());
      assertEquals(Arrays.asList(new Byte((byte) 55)), trt.rBytes());
      assertEquals(Arrays.asList(new Short((short) 55)), trt.rShorts());
      assertEquals(Arrays.asList(Integer.valueOf(55)), trt.rInts());
      assertEquals(Arrays.asList(new Long(55L)), trt.rLongs());
      assertEquals(Arrays.asList(new Float(55F)), trt.rFloats());
      assertEquals(Arrays.asList(new Double(55D)), trt.rDoubles());
      assertEquals(Arrays.asList("55"), trt.rStrings());
      assertEquals(Arrays.asList(new URI("55")), trt.rURIs());

      assertTrue(Arrays.equals(new Boolean[] {true}, trt.raBoolean()));
      assertTrue(Arrays.equals(new Byte[] {55}, trt.raByte()));
      assertTrue(Arrays.equals(new Short[] {55}, trt.raShort()));
      assertTrue(Arrays.equals(new Integer[] {55}, trt.raInt()));
      assertTrue(Arrays.equals(new Long[] {55L}, trt.raLong()));
      assertTrue(Arrays.equals(new Float[] {55F}, trt.raFloat()));
      assertTrue(Arrays.equals(new Double[] {55D}, trt.raDouble()));
      assertTrue(Arrays.equals(new String[] {"55"}, trt.raString()));
      assertTrue(Arrays.equals(new URI[] {new URI("55")}, trt.raURI()));
    }

    {
      // Test single value
      TestReturnTypes trt = set(TestReturnTypes.class, 55);
      assertEquals(true, trt.rpBoolean());
      assertEquals(55, trt.rpByte());
      assertEquals(55, trt.rpShort());
      assertEquals(55, trt.rpInt());
      assertEquals(55L, trt.rpLong());
      assertEquals(55.0D, trt.rpDouble());
      assertEquals(55.0F, trt.rpFloat());
      assertEquals((Boolean) true, trt.rBoolean());
      assertEquals(new Byte((byte) 55), trt.rByte());
      assertEquals(new Short((short) 55), trt.rShort());
      assertEquals(Integer.valueOf(55), trt.rInt());
      assertEquals(new Long(55L), trt.rLong());
      assertEquals(new Float(55F), trt.rFloat());
      assertEquals(new Double(55), trt.rDouble());
      assertEquals("55", trt.rString());
      assertEquals(new URI("55"), trt.rURI());
      assertTrue(Arrays.equals(new boolean[] {true}, trt.rpaBoolean()));
      assertTrue(Arrays.equals(new byte[] {55}, trt.rpaByte()));
      assertTrue(Arrays.equals(new short[] {55}, trt.rpaShort()));
      assertTrue(Arrays.equals(new int[] {55}, trt.rpaInt()));
      assertTrue(Arrays.equals(new long[] {55}, trt.rpaLong()));
      assertTrue(Arrays.equals(new float[] {55}, trt.rpaFloat()));
      assertTrue(Arrays.equals(new double[] {55}, trt.rpaDouble()));
      assertEquals(Arrays.asList(true), trt.rBooleans());
      assertEquals(Arrays.asList(new Byte((byte) 55)), trt.rBytes());
      assertEquals(Arrays.asList(new Short((short) 55)), trt.rShorts());
      assertEquals(Arrays.asList(Integer.valueOf(55)), trt.rInts());
      assertEquals(Arrays.asList(new Long(55L)), trt.rLongs());
      assertEquals(Arrays.asList(new Float(55F)), trt.rFloats());
      assertEquals(Arrays.asList(new Double(55D)), trt.rDoubles());
      assertEquals(Arrays.asList("55"), trt.rStrings());
      assertEquals(Arrays.asList(new URI("55")), trt.rURIs());

      assertTrue(Arrays.equals(new Boolean[] {true}, trt.raBoolean()));
      assertTrue(Arrays.equals(new Byte[] {55}, trt.raByte()));
      assertTrue(Arrays.equals(new Short[] {55}, trt.raShort()));
      assertTrue(Arrays.equals(new Integer[] {55}, trt.raInt()));
      assertTrue(Arrays.equals(new Long[] {55L}, trt.raLong()));
      assertTrue(Arrays.equals(new Float[] {55F}, trt.raFloat()));
      assertTrue(Arrays.equals(new Double[] {55D}, trt.raDouble()));
      assertTrue(Arrays.equals(new String[] {"55"}, trt.raString()));
      assertTrue(Arrays.equals(new URI[] {new URI("55")}, trt.raURI()));
    }
  }

  static <T> T set(Class<T> interf, Object value) {
    Properties p = new Properties();
    Method ms[] = interf.getMethods();

    for (Method m : ms) {
      p.put(m.getName(), value);
    }
    return Configurable.createConfigurable(interf, (Map<Object, Object>) p);
  }

  /** Test enum handling */
  @Meta.OCD
  public static interface Enums {
    enum X {
      requireConfiguration,
      optionalConfiguration,
      ignoreConfiguration
    }

    X r();

    X i();

    X o();
  }

  public static void testEnum() throws Exception {
    Builder b = new Builder();
    b.addClasspath(new File("bin"));
    b.setProperty("Export-Package", "test.metatype");
    b.setProperty("-metatype", "*");
    b.build();
    assertEquals(0, b.getErrors().size());
    assertEquals(0, b.getWarnings().size());

    Resource r = b.getJar().getResource("OSGI-INF/metatype/test.metatype.MetatypeTest$Enums.xml");
    IO.copy(r.openInputStream(), System.err);

    Document d = db.parse(r.openInputStream());
    assertEquals(
        "http://www.osgi.org/xmlns/metatype/v1.1.0", d.getDocumentElement().getNamespaceURI());

    Properties p = new Properties();
    p.setProperty("r", "requireConfiguration");
    p.setProperty("i", "ignoreConfiguration");
    p.setProperty("o", "optionalConfiguration");
    Enums enums = Configurable.createConfigurable(Enums.class, (Map<Object, Object>) p);
    assertEquals(Enums.X.requireConfiguration, enums.r());
    assertEquals(Enums.X.ignoreConfiguration, enums.i());
    assertEquals(Enums.X.optionalConfiguration, enums.o());
  }

  /** Test the OCD settings */
  @Meta.OCD()
  public static interface OCDEmpty {}

  @Meta.OCD(description = "description")
  public static interface OCDDescription {}

  @Meta.OCD()
  public static interface OCDDesignatePidOnly {}

  @Meta.OCD(factory = true)
  public static interface OCDDesignatePidFactory {}

  @Meta.OCD(id = "id")
  public static interface OCDId {}

  @Meta.OCD(id = "id")
  public static interface OCDIdWithPid {}

  @Meta.OCD(localization = "localization")
  public static interface OCDLocalization {}

  @Meta.OCD(name = "name")
  public static interface OCDName {}

  public static void testOCD() throws Exception {
    Builder b = new Builder();
    b.addClasspath(new File("bin"));
    b.setProperty("Export-Package", "test.metatype");
    b.setProperty("-metatype", "*");
    b.build();
    assertEquals(0, b.getErrors().size());
    assertEquals(0, b.getWarnings().size());
    System.err.println(b.getJar().getResources().keySet());

    assertOCD(
        b,
        "test.metatype.MetatypeTest$OCDEmpty",
        "test.metatype.MetatypeTest$OCDEmpty",
        "Metatype test OCDEmpty",
        null,
        "test.metatype.MetatypeTest$OCDEmpty",
        false,
        null);
    assertOCD(
        b,
        "test.metatype.MetatypeTest$OCDName",
        "test.metatype.MetatypeTest$OCDName",
        "name",
        null,
        "test.metatype.MetatypeTest$OCDName",
        false,
        null);
    assertOCD(
        b,
        "test.metatype.MetatypeTest$OCDDescription",
        "test.metatype.MetatypeTest$OCDDescription",
        "Metatype test OCDDescription",
        "description",
        "test.metatype.MetatypeTest$OCDDescription",
        false,
        null);
    assertOCD(
        b,
        "test.metatype.MetatypeTest$OCDDesignatePidOnly",
        "test.metatype.MetatypeTest$OCDDesignatePidOnly",
        "Metatype test OCDDesignate pid only",
        null,
        "test.metatype.MetatypeTest$OCDDesignatePidOnly",
        false,
        null);
    assertOCD(
        b,
        "test.metatype.MetatypeTest$OCDDesignatePidFactory",
        "test.metatype.MetatypeTest$OCDDesignatePidFactory",
        "Metatype test OCDDesignate pid factory",
        null,
        "test.metatype.MetatypeTest$OCDDesignatePidFactory",
        true,
        null);
    assertOCD(
        b,
        "test.metatype.MetatypeTest$OCDId",
        "id",
        "Metatype test OCDId",
        null,
        "id",
        false,
        null);
    assertOCD(
        b,
        "test.metatype.MetatypeTest$OCDIdWithPid",
        "id",
        "Metatype test OCDId with pid",
        null,
        "id",
        false,
        null);
    assertOCD(
        b,
        "test.metatype.MetatypeTest$OCDLocalization",
        "test.metatype.MetatypeTest$OCDLocalization",
        "Metatype test OCDLocalization",
        null,
        "test.metatype.MetatypeTest$OCDLocalization",
        false,
        "localization");
  }

  static void assertOCD(
      Builder b,
      String cname,
      String id,
      String name,
      String description,
      String designate,
      boolean factory,
      String localization)
      throws Exception {
    Resource r = b.getJar().getResource("OSGI-INF/metatype/" + cname + ".xml");
    assertNotNull(r);
    IO.copy(r.openInputStream(), System.err);
    Document d = db.parse(r.openInputStream());
    assertEquals(id, xpath.evaluate("//OCD/@id", d, XPathConstants.STRING));
    assertEquals(name, xpath.evaluate("//OCD/@name", d, XPathConstants.STRING));
    assertEquals(
        localization == null ? cname : localization,
        xpath.evaluate("//OCD/@localization", d, XPathConstants.STRING));
    assertEquals(
        description == null ? "" : description,
        xpath.evaluate("//OCD/@description", d, XPathConstants.STRING));

    if (designate == null) {
      assertEquals(id, xpath.evaluate("//Designate/@pid", d, XPathConstants.STRING));
      if (factory)
        assertEquals(id, xpath.evaluate("//Designate/@factoryPid", d, XPathConstants.STRING));
    } else {
      assertEquals(designate, xpath.evaluate("//Designate/@pid", d, XPathConstants.STRING));
      if (factory)
        assertEquals(
            designate, xpath.evaluate("//Designate/@factoryPid", d, XPathConstants.STRING));
    }

    assertEquals(id, xpath.evaluate("//Object/@ocdref", d, XPathConstants.STRING));
  }

  /** Test the AD settings. */
  @Meta.OCD(description = "advariations")
  public static interface TestAD {
    @Meta.AD
    String noSettings();

    @Meta.AD(id = "id")
    String withId();

    @Meta.AD(name = "name")
    String withName();

    @Meta.AD(max = "1")
    String withMax();

    @Meta.AD(min = "-1")
    String withMin();

    @Meta.AD(deflt = "deflt")
    String withDefault();

    @Meta.AD(cardinality = 0)
    String[] withC0();

    @Meta.AD(cardinality = 1)
    String[] withC1();

    @Meta.AD(cardinality = -1)
    Collection<String> withC_1();

    @Meta.AD(cardinality = -1)
    String[] withC_1ButArray();

    @Meta.AD(cardinality = 1)
    Collection<String> withC1ButCollection();

    @Meta.AD(type = Meta.Type.String)
    int withInt();

    @Meta.AD(type = Meta.Type.Integer)
    String withString();

    @Meta.AD(description = "description_xxx\"xxx'xxx")
    String a();

    @Meta.AD(optionValues = {"a", "b"})
    String valuesOnly();

    @Meta.AD(
        optionValues = {"a", "b"},
        optionLabels = {"A", "B"})
    String labelsAndValues();

    @Meta.AD(required = true)
    String required();

    @Meta.AD(required = false)
    String notRequired();
  }

  public static void testAD() throws Exception {
    Builder b = new Builder();
    b.addClasspath(new File("bin"));
    b.setProperty("Export-Package", "test.metatype");
    b.setProperty("-metatype", "*");
    b.build();
    Resource r = b.getJar().getResource("OSGI-INF/metatype/test.metatype.MetatypeTest$TestAD.xml");
    assertEquals(0, b.getErrors().size());
    assertEquals(0, b.getWarnings().size());
    System.err.println(b.getJar().getResources().keySet());
    assertNotNull(r);
    IO.copy(r.openInputStream(), System.err);

    Document d = db.parse(r.openInputStream());

    assertAD(
        d,
        "noSettings",
        "No settings",
        "noSettings",
        null,
        null,
        null,
        0,
        "String",
        null,
        null,
        null);
    assertAD(d, "withId", "With id", "id", null, null, null, 0, "String", null, null, null);
    assertAD(d, "name", "name", "withName", null, null, null, 0, "String", null, null, null);
    assertAD(d, "withMax", "With max", "withMax", null, "1", null, 0, "String", null, null, null);
    assertAD(d, "withMin", "With min", "withMin", "-1", null, null, 0, "String", null, null, null);
    assertAD(d, "withC1", "With c1", "withC1", null, null, null, 1, "String", null, null, null);
    assertAD(
        d, "withC0", "With c0", "withC0", null, null, null, 2147483647, "String", null, null, null);
    assertAD(d, "withC_1", "With c 1", "withC.1", null, null, null, -1, "String", null, null, null);
    assertAD(
        d,
        "withC_1ButArray",
        "With c 1 but array",
        "withC.1ButArray",
        null,
        null,
        null,
        -1,
        "String",
        null,
        null,
        null);
    assertAD(
        d,
        "withC1ButCollection",
        "With c1 but collection",
        "withC1ButCollection",
        null,
        null,
        null,
        1,
        "String",
        null,
        null,
        null);
    assertAD(d, "withInt", "With int", "withInt", null, null, null, 0, "String", null, null, null);
    assertAD(
        d,
        "withString",
        "With string",
        "withString",
        null,
        null,
        null,
        0,
        "Integer",
        null,
        null,
        null);
    assertAD(
        d, "a", "A", "a", null, null, null, 0, "String", "description_xxx\"xxx'xxx", null, null);
    assertAD(
        d,
        "valuesOnly",
        "Values only",
        "valuesOnly",
        null,
        null,
        null,
        0,
        "String",
        null,
        new String[] {"a", "b"},
        new String[] {"a", "b"});
    assertAD(
        d,
        "labelsAndValues",
        "Labels and values",
        "labelsAndValues",
        null,
        null,
        null,
        0,
        "String",
        null,
        new String[] {"a", "b"},
        new String[] {"A", "A"});
  }

  /** Test the AD inheritance. */
  @Meta.OCD(description = "adinheritance-super-one")
  public static interface TestADWithInheritanceSuperOne {
    @Meta.AD
    String fromSuperOne();
  }

  @Meta.OCD(description = "adinheritance-super")
  public static interface TestADWithInheritanceSuperTwo {
    @Meta.AD
    String fromSuperTwo();
  }

  @Meta.OCD(description = "adinheritance-child")
  public static interface TestADWithInheritanceChild
      extends TestADWithInheritanceSuperOne, TestADWithInheritanceSuperTwo {
    @Meta.AD
    String fromChild();
  }

  public static void testADWithInheritance() throws Exception {
    Builder b = new Builder();
    b.addClasspath(new File("bin"));
    b.setProperty("Export-Package", "test.metatype");
    b.setProperty("-metatype", "*");
    b.setProperty("-metatype-inherit", "true");
    b.build();
    Resource r =
        b.getJar()
            .getResource(
                "OSGI-INF/metatype/test.metatype.MetatypeTest$TestADWithInheritanceChild.xml");
    assertEquals(0, b.getErrors().size());
    assertEquals(0, b.getWarnings().size());
    System.err.println(b.getJar().getResources().keySet());
    assertNotNull(r);
    IO.copy(r.openInputStream(), System.err);

    Document d = db.parse(r.openInputStream());

    assertAD(
        d, "fromChild", "From child", "fromChild", null, null, null, 0, "String", null, null, null);
    assertAD(
        d,
        "fromSuperOne",
        "From super one",
        "fromSuperOne",
        null,
        null,
        null,
        0,
        "String",
        null,
        null,
        null);
    assertAD(
        d,
        "fromSuperTwo",
        "From super two",
        "fromSuperTwo",
        null,
        null,
        null,
        0,
        "String",
        null,
        null,
        null);
  }

  static void assertAD(
      Document d,
      @SuppressWarnings("unused") String mname,
      String name,
      String id,
      String min,
      String max,
      String deflt,
      int cardinality,
      String type,
      String description,
      @SuppressWarnings("unused") String[] optionvalues,
      @SuppressWarnings("unused") String optionLabels[])
      throws XPathExpressionException {
    assertEquals(
        name, xpath.evaluate("//OCD/AD[@id='" + id + "']/@name", d, XPathConstants.STRING));
    assertEquals(id, xpath.evaluate("//OCD/AD[@id='" + id + "']/@id", d, XPathConstants.STRING));
    assertEquals(
        min == null ? "" : min,
        xpath.evaluate("//OCD/AD[@id='" + id + "']/@min", d, XPathConstants.STRING));
    assertEquals(
        max == null ? "" : max,
        xpath.evaluate("//OCD/AD[@id='" + id + "']/@max", d, XPathConstants.STRING));
    assertEquals(
        deflt == null ? "" : deflt,
        xpath.evaluate("//OCD/AD[@id='" + id + "']/@deflt", d, XPathConstants.STRING));
    assertEquals(
        cardinality + "",
        xpath.evaluate("//OCD/AD[@id='" + id + "']/@cardinality", d, XPathConstants.STRING));
    assertEquals(
        type, xpath.evaluate("//OCD/AD[@id='" + id + "']/@type", d, XPathConstants.STRING));
    assertEquals(
        description == null ? "" : description,
        xpath.evaluate("//OCD/AD[@id='" + id + "']/@description", d, XPathConstants.STRING));
  }

  /** Test all the return types. */
  @Meta.OCD(description = "simple", name = "TestSimple")
  public static interface TestReturnTypes {
    boolean rpBoolean();

    byte rpByte();

    char rpCharacter();

    short rpShort();

    int rpInt();

    long rpLong();

    float rpFloat();

    double rpDouble();

    Boolean rBoolean();

    Byte rByte();

    Character rCharacter();

    Short rShort();

    Integer rInt();

    Long rLong();

    Float rFloat();

    Double rDouble();

    String rString();

    URI rURI();

    boolean[] rpaBoolean();

    byte[] rpaByte();

    char[] rpaCharacter();

    short[] rpaShort();

    int[] rpaInt();

    long[] rpaLong();

    float[] rpaFloat();

    double[] rpaDouble();

    Collection<Boolean> rBooleans();

    Collection<Byte> rBytes();

    Collection<Character> rCharacters();

    Collection<Short> rShorts();

    Collection<Integer> rInts();

    Collection<Long> rLongs();

    Collection<Float> rFloats();

    Collection<Double> rDoubles();

    Collection<String> rStrings();

    Collection<URI> rURIs();

    Boolean[] raBoolean();

    Byte[] raByte();

    Character[] raCharacter();

    Short[] raShort();

    Integer[] raInt();

    Long[] raLong();

    Float[] raFloat();

    Double[] raDouble();

    String[] raString();

    URI[] raURI();
  }

  public static void testReturnTypes() throws Exception {
    Builder b = new Builder();
    b.addClasspath(new File("bin"));
    b.setProperty("Export-Package", "test.metatype");
    b.setProperty("-metatype", "*");
    b.build();
    Resource r =
        b.getJar().getResource("OSGI-INF/metatype/test.metatype.MetatypeTest$TestReturnTypes.xml");
    assertEquals(0, b.getErrors().size());
    assertEquals(0, b.getWarnings().size());
    System.err.println(b.getJar().getResources().keySet());
    assertNotNull(r);
    IO.copy(r.openInputStream(), System.err);

    Document d = db.parse(r.openInputStream());
    assertEquals(
        "http://www.osgi.org/xmlns/metatype/v1.1.0", d.getDocumentElement().getNamespaceURI());
    // Primitives
    assertEquals("Boolean", xpath.evaluate("//OCD/AD[@id='rpBoolean']/@type", d));
    assertEquals("Byte", xpath.evaluate("//OCD/AD[@id='rpByte']/@type", d));
    assertEquals("Character", xpath.evaluate("//OCD/AD[@id='rpCharacter']/@type", d));
    assertEquals("Short", xpath.evaluate("//OCD/AD[@id='rpShort']/@type", d));
    assertEquals("Integer", xpath.evaluate("//OCD/AD[@id='rpInt']/@type", d));
    assertEquals("Long", xpath.evaluate("//OCD/AD[@id='rpLong']/@type", d));
    assertEquals("Float", xpath.evaluate("//OCD/AD[@id='rpFloat']/@type", d));
    assertEquals("Double", xpath.evaluate("//OCD/AD[@id='rpDouble']/@type", d));

    // Primitive Wrappers
    assertEquals("Boolean", xpath.evaluate("//OCD/AD[@id='rBoolean']/@type", d));
    assertEquals("Byte", xpath.evaluate("//OCD/AD[@id='rByte']/@type", d));
    assertEquals("Character", xpath.evaluate("//OCD/AD[@id='rCharacter']/@type", d));
    assertEquals("Short", xpath.evaluate("//OCD/AD[@id='rShort']/@type", d));
    assertEquals("Integer", xpath.evaluate("//OCD/AD[@id='rInt']/@type", d));
    assertEquals("Long", xpath.evaluate("//OCD/AD[@id='rLong']/@type", d));
    assertEquals("Float", xpath.evaluate("//OCD/AD[@id='rFloat']/@type", d));
    assertEquals("Double", xpath.evaluate("//OCD/AD[@id='rDouble']/@type", d));

    // Primitive Arrays
    assertEquals("Boolean", xpath.evaluate("//OCD/AD[@id='rpaBoolean']/@type", d));
    assertEquals("Byte", xpath.evaluate("//OCD/AD[@id='rpaByte']/@type", d));
    assertEquals("Character", xpath.evaluate("//OCD/AD[@id='rpaCharacter']/@type", d));
    assertEquals("Short", xpath.evaluate("//OCD/AD[@id='rpaShort']/@type", d));
    assertEquals("Integer", xpath.evaluate("//OCD/AD[@id='rpaInt']/@type", d));
    assertEquals("Long", xpath.evaluate("//OCD/AD[@id='rpaLong']/@type", d));
    assertEquals("Float", xpath.evaluate("//OCD/AD[@id='rpaFloat']/@type", d));
    assertEquals("Double", xpath.evaluate("//OCD/AD[@id='rpaDouble']/@type", d));

    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='rpaBoolean']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='rpaByte']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='rpaCharacter']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='rpaShort']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='rpaInt']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='rpaLong']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='rpaFloat']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='rpaDouble']/@cardinality", d));

    // Wrapper + Object arrays
    assertEquals("Boolean", xpath.evaluate("//OCD/AD[@id='raBoolean']/@type", d));
    assertEquals("Byte", xpath.evaluate("//OCD/AD[@id='raByte']/@type", d));
    assertEquals("Character", xpath.evaluate("//OCD/AD[@id='raCharacter']/@type", d));
    assertEquals("Short", xpath.evaluate("//OCD/AD[@id='raShort']/@type", d));
    assertEquals("Integer", xpath.evaluate("//OCD/AD[@id='raInt']/@type", d));
    assertEquals("Long", xpath.evaluate("//OCD/AD[@id='raLong']/@type", d));
    assertEquals("Float", xpath.evaluate("//OCD/AD[@id='raFloat']/@type", d));
    assertEquals("Double", xpath.evaluate("//OCD/AD[@id='raDouble']/@type", d));
    assertEquals("String", xpath.evaluate("//OCD/AD[@id='raString']/@type", d));
    assertEquals("String", xpath.evaluate("//OCD/AD[@id='raURI']/@type", d));

    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='raBoolean']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='raByte']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='raCharacter']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='raShort']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='raInt']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='raLong']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='raFloat']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='raDouble']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='raString']/@cardinality", d));
    assertEquals("2147483647", xpath.evaluate("//OCD/AD[@id='raURI']/@cardinality", d));

    // Wrapper + Object collections
    assertEquals("Boolean", xpath.evaluate("//OCD/AD[@id='rBooleans']/@type", d));
    assertEquals("Byte", xpath.evaluate("//OCD/AD[@id='rBytes']/@type", d));
    assertEquals("Character", xpath.evaluate("//OCD/AD[@id='rCharacter']/@type", d));
    assertEquals("Short", xpath.evaluate("//OCD/AD[@id='rShorts']/@type", d));
    assertEquals("Integer", xpath.evaluate("//OCD/AD[@id='rInts']/@type", d));
    assertEquals("Long", xpath.evaluate("//OCD/AD[@id='rLongs']/@type", d));
    assertEquals("Float", xpath.evaluate("//OCD/AD[@id='rFloats']/@type", d));
    assertEquals("Double", xpath.evaluate("//OCD/AD[@id='rDoubles']/@type", d));
    assertEquals("String", xpath.evaluate("//OCD/AD[@id='rStrings']/@type", d));
    assertEquals("String", xpath.evaluate("//OCD/AD[@id='rURIs']/@type", d));

    assertEquals("-2147483648", xpath.evaluate("//OCD/AD[@id='rBooleans']/@cardinality", d));
    assertEquals("-2147483648", xpath.evaluate("//OCD/AD[@id='rBytes']/@cardinality", d));
    assertEquals("-2147483648", xpath.evaluate("//OCD/AD[@id='rCharacters']/@cardinality", d));
    assertEquals("-2147483648", xpath.evaluate("//OCD/AD[@id='rShorts']/@cardinality", d));
    assertEquals("-2147483648", xpath.evaluate("//OCD/AD[@id='rInts']/@cardinality", d));
    assertEquals("-2147483648", xpath.evaluate("//OCD/AD[@id='rLongs']/@cardinality", d));
    assertEquals("-2147483648", xpath.evaluate("//OCD/AD[@id='rFloats']/@cardinality", d));
    assertEquals("-2147483648", xpath.evaluate("//OCD/AD[@id='rDoubles']/@cardinality", d));
    assertEquals("-2147483648", xpath.evaluate("//OCD/AD[@id='rStrings']/@cardinality", d));
    assertEquals("-2147483648", xpath.evaluate("//OCD/AD[@id='rURIs']/@cardinality", d));
  }

  /**
   * Test simple
   *
   * @author aqute
   */
  @Meta.OCD(description = "simple", name = "TestSimple")
  public static interface TestSimple {
    @Meta.AD
    String simple();

    String[] notSoSimple();

    Collection<String> stringCollection();

    @Meta.AD(deflt = "true")
    boolean enabled();
  }

  public static void testSimple() throws Exception {
    Builder b = new Builder();
    b.addClasspath(new File("bin"));
    b.setProperty("Export-Package", "test.metatype");
    b.setProperty("-metatype", "*");
    b.build();
    Resource r =
        b.getJar().getResource("OSGI-INF/metatype/test.metatype.MetatypeTest$TestSimple.xml");
    assertEquals(0, b.getErrors().size());
    assertEquals(0, b.getWarnings().size());
    System.err.println(b.getJar().getResources().keySet());
    assertNotNull(r);
    IO.copy(r.openInputStream(), System.err);

    Document d = db.parse(r.openInputStream());

    assertEquals("TestSimple", xpath.evaluate("//OCD/@name", d));
    assertEquals("simple", xpath.evaluate("//OCD/@description", d));
    assertEquals("test.metatype.MetatypeTest$TestSimple", xpath.evaluate("//OCD/@id", d));
    assertEquals("test.metatype.MetatypeTest$TestSimple", xpath.evaluate("//Designate/@pid", d));
    assertEquals("test.metatype.MetatypeTest$TestSimple", xpath.evaluate("//Object/@ocdref", d));
    assertEquals("simple", xpath.evaluate("//OCD/AD[@id='simple']/@id", d));
    assertEquals("Simple", xpath.evaluate("//OCD/AD[@id='simple']/@name", d));
    assertEquals("String", xpath.evaluate("//OCD/AD[@id='simple']/@type", d));
    assertEquals("true", xpath.evaluate("//OCD/AD[@id='notSoSimple']/@required", d));
    /**
     * https://github.com/bndtools/bnd/issues/281
     *
     * <p>Using the Bnd annotations library (1.52.3), the generated metatype file will have
     * required='false' for all fields annotated with @Meta.AD(). When this annotation is omitted,
     * or when the required property is explicitly set, the field is correctly marked as required.
     * Taking a glance at the code, the bug appears to be due to aQute.bnd.osgi.Annotation using
     * aQute.bnd.annotation.metatype.Configurable internally for bridging Bnd-annotations to
     * Java-annotations. This configurable only obtains the values from the Bnd-annotation, omitting
     * the defaults defined in the Java annotation. The workaround is to explicitly mention the
     * required property on each field annotated with @Meta.AD.
     */
    assertEquals("true", xpath.evaluate("//OCD/AD[@id='simple']/@required", d));
    assertEquals(
        Integer.MAX_VALUE + "", xpath.evaluate("//OCD/AD[@id='notSoSimple']/@cardinality", d));
  }

  /**
   * https://github.com/bndtools/bnd/issues/316 Example Configuration:
   *
   * <pre>
   *  @Meta.AD(required = false, type = Type.Boolean, deflt = "false")
   *  boolean enabled();
   * It appears that in the configurable class that this logic
   *
   * if (resultType == boolean.class || resultType == Boolean.class) {
   *         if ( actualType == boolean.class || actualType ==   Boolean.class)
   *             return o;
   *
   *         if (Number.class.isAssignableFrom(actualType)) {
   *             double b = ((Number) o).doubleValue();
   *             if (b == 0)
   *                 return false;
   *             else
   *                 return true;
   *         }
   *         return true;
   * </pre>
   *
   * Does not perform as expected. The deflt value from the configuration interface will always be a
   * string, and the value is never parsed, therefore the third if statement is basically
   * unreachable for a default value. Additionally the default behavior of returning true is
   * unexpected because default values for booleans is false, configuration admin would use false
   * for anything NOT equal, ignore case "true", so why would this be true, and how could that
   * assumption even be made when, at least in the aforementioned case of the default value, the
   * incoming value isn't processed(parsed, or in someway checked for actual content). Note that per
   * documentation available an number value was tried for the deflt, ie "0", but again the value
   * isn't processed so this had no effect.
   */
  static interface DefaultBoolean {
    @Meta.AD(deflt = "true", required = false)
    boolean istrue();

    @Meta.AD(deflt = "FALSE", required = false)
    boolean isfalse();

    @Meta.AD(required = false)
    boolean isAlsoFalse();
  }

  public static void testConfigurable() {
    Map<String, Object> ht = new Hashtable<String, Object>();
    DefaultBoolean db = Configurable.createConfigurable(DefaultBoolean.class, ht);
    assertTrue(db.istrue());
    assertFalse(db.isfalse());
    assertFalse(db.isAlsoFalse());
  }
}