/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.felix.ipojo.parser; import java.util.ArrayList; import java.util.Dictionary; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.felix.ipojo.Factory; import org.apache.felix.ipojo.metadata.Attribute; import org.apache.felix.ipojo.metadata.Element; /** * The Manifest Metadata parser reads a manifest file and builds * the iPOJO metadata ({@link Element} / {@link Attribute} ) structure. * * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> */ public class ManifestMetadataParser { /** * The element list. * Contains the element found in the parsed header. */ private Element[] m_elements = new Element[0]; /** * Gets the array of component type metadata. * @return the component metadata (composite & component). * An empty array is returned if no component type declaration. * @throws ParseException if a parsing error occurs */ public Element[] getComponentsMetadata() throws ParseException { Element[] elems = m_elements[0].getElements(); List list = new ArrayList(); for (int i = 0; i < elems.length; i++) { if (!"instance".equals(elems[i].getName())) { list.add(elems[i]); } } return (Element[]) list.toArray(new Element[list.size()]); } /** * Gets the array of instance configuration described in the metadata. * @return the instances list or <code>null</code> if no instance configuration. * @throws ParseException if the metadata cannot be parsed successfully */ public Dictionary[] getInstances() throws ParseException { Element[] configs = m_elements[0].getElements("instance"); if (configs == null) { return null; } Dictionary[] dicts = new Dictionary[configs.length]; for (int i = 0; i < configs.length; i++) { dicts[i] = parseInstance(configs[i]); } return dicts; } /** * Parses an Element to create an instance configuration dictionary. * The 'name' attribute of the instance declaration is mapped * to the 'instance.name' property. * @param instance the Element describing an instance. * @return the resulting dictionary * @throws ParseException if a configuration cannot be parse correctly. */ private Dictionary parseInstance(Element instance) throws ParseException { Dictionary dict = new Properties(); String name = instance.getAttribute("name"); String comp = instance.getAttribute("component"); String version = instance.getAttribute("version"); if (name != null) { dict.put(Factory.INSTANCE_NAME_PROPERTY, instance.getAttribute("name")); } if (comp == null) { throw new ParseException("An instance does not have the 'component' attribute"); } dict.put("component", comp); if (version != null) { dict.put(Factory.FACTORY_VERSION_PROPERTY, version); } Element[] props = instance.getElements("property"); for (int i = 0; props != null && i < props.length; i++) { parseProperty(props[i], dict); } return dict; } /** * Parses an instance property. * This method is recursive to handle complex properties. * @param prop the current element to parse * @param dict the dictionary to populate (the future instance configuration) * @throws ParseException if the property cannot be parsed correctly */ private void parseProperty(Element prop, Dictionary dict) throws ParseException { // Check that the property has a name String name = prop.getAttribute("name"); String value = prop.getAttribute("value"); if (name == null) { throw new ParseException("A property does not have the 'name' attribute: " + prop); } //case : the property element has no 'value' attribute if (value == null) { // Recursive case // Get the type of the structure to create String type = prop.getAttribute("type"); if (type == null || type.equalsIgnoreCase("dictionary")) { dict.put(name, parseDictionary(prop)); } else if (type.equalsIgnoreCase("map")) { dict.put(name, parseMap(prop)); } else if (type.equalsIgnoreCase("list")) { dict.put(name, parseList(prop)); } else if (type.equalsIgnoreCase("array")) { List list = parseList(prop); boolean isString = true; for (int i = 0; isString && i < list.size(); i++) { isString = list.get(i) instanceof String; } Object[] obj = null; if (isString) { obj = new String[list.size()]; } else { obj = new Object[list.size()]; } // Transform the list to array dict.put(name, list.toArray(obj)); } } else { dict.put(prop.getAttribute("name"), prop.getAttribute("value")); } } /** * Parses a complex property. * This property will be built as a {@link Dictionary}. * @param prop the Element to parse. * @return the resulting dictionary * @throws ParseException if an internal property is incorrect. */ private Dictionary parseDictionary(Element prop) throws ParseException { // Check if there is 'property' elements Element[] subProps = prop.getElements("property"); if (subProps != null) { Dictionary dict2 = new Properties(); for (int i = 0; i < subProps.length; i++) { parseProperty(subProps[i], dict2); } return dict2; } else { // If the no sub-properties, inject an empty dictionary. return new Properties(); } } /** * Parses a complex property. * This property will be built as a {@link Map}. * The used {@link Map} implementation is {@link HashMap}. * @param prop the property to parse * @return the resulting Map * @throws ParseException if an internal property is incorrect. */ private Map parseMap(Element prop) throws ParseException { // Check if there is 'property' elements Element[] subProps = prop.getElements("property"); if (subProps != null) { Map map = new HashMap(); // Create an hashmap to store elements. for (int i = 0; i < subProps.length; i++) { parseProperty(subProps[i], map); } return map; } else { // if not inject an empty map return new HashMap(0); } } /** * Parses a complex property. This property will be built as a {@link List}. * The used {@link List} implementation is {@link ArrayList}. * The order of elements is kept. * @param prop the property to parse * @return the resulting List * @throws ParseException if an internal property is incorrect. */ private List parseList(Element prop) throws ParseException { Element[] subProps = prop.getElements("property"); if (subProps != null) { List list = new ArrayList(subProps.length); // Create a list to store elements. for (int i = 0; i < subProps.length; i++) { parseAnonymousProperty(subProps[i], list); // Anonymous properties. } return list; } else { // If no sub-properties, inject an empty list. return new ArrayList(0); } } /** * Parse a property. * This methods handles complex properties. * @param prop the current element to parse * @param map the map to populate * @throws ParseException if the property cannot be parsed correctly */ private void parseProperty(Element prop, Map map) throws ParseException { // Check that the property has a name String name = prop.getAttribute("name"); String value = prop.getAttribute("value"); if (name == null) { throw new ParseException( "A property does not have the 'name' attribute"); } // case : the property element has no 'value' attribute if (value == null) { // Recursive case // Get the type of the structure to create String type = prop.getAttribute("type"); if (type == null || type.equalsIgnoreCase("dictionary")) { map.put(name, parseDictionary(prop)); } else if (type.equalsIgnoreCase("map")) { map.put(name, parseMap(prop)); } else if (type.equalsIgnoreCase("list")) { map.put(name, parseList(prop)); } else if (type.equalsIgnoreCase("array")) { List list = parseList(prop); boolean isString = true; for (int i = 0; isString && i < list.size(); i++) { isString = list.get(i) instanceof String; } Object[] obj = null; if (isString) { obj = new String[list.size()]; } else { obj = new Object[list.size()]; } map.put(name, list.toArray(obj)); // Transform the list to array } } else { map.put(prop.getAttribute("name"), prop.getAttribute("value")); } } /** * Parse an anonymous property. * An anonymous property is a property with no name. * An anonymous property can be simple (just a value) or complex (i.e. a map, a dictionary * a list or an array). * @param prop the property to parse * @param list the list to populate with the resulting property * @throws ParseException if an internal property cannot be parse correctly */ private void parseAnonymousProperty(Element prop, List list) throws ParseException { // Check that the property has a name String name = prop.getAttribute("name"); String value = prop.getAttribute("value"); if (name != null) { throw new ParseException("Anonymous property expected in a list or an array"); } //case : the property element has no 'value' attribute if (value == null) { // Recursive case // Get the type of the structure to create String type = prop.getAttribute("type"); if (type == null || type.equalsIgnoreCase("dictionary")) { // Check if there is 'property' elements Element[] subProps = prop.getElements("property"); if (subProps != null) { Dictionary dict2 = new Properties(); for (int i = 0; i < subProps.length; i++) { parseProperty(subProps[i], dict2); } list.add(dict2); } else { // If the no sub-properties, inject an empty dictionary. list.add(new Properties()); } } else if (type.equalsIgnoreCase("map")) { // Check if there is 'property' elements Element[] subProps = prop.getElements("property"); if (subProps != null) { Map map2 = new HashMap(); // Create an hashmap to store elements. for (int i = 0; i < subProps.length; i++) { parseProperty(subProps[i], map2); } list.add(map2); } else { // if not inject an empty map list.add(new HashMap(0)); } } else if (type.equalsIgnoreCase("list")) { Element[] subProps = prop.getElements("property"); if (subProps != null) { // Create a list to store elements. List list2 = new ArrayList(subProps.length); for (int i = 0; i < subProps.length; i++) { parseAnonymousProperty(subProps[i], list2); // Anonymous properties } list.add(list2); } else { // If no sub-properties, inject an empty list. list.add(new ArrayList(0)); } } else if (type.equalsIgnoreCase("array")) { // Check sub-props. Element[] subProps = prop.getElements("property"); if (subProps != null) { List list2 = new ArrayList(subProps.length); // Use list as // pivot type for (int i = 0; i < subProps.length; i++) { parseAnonymousProperty(subProps[i], list2); } list.add(list.toArray(new Object[list.size()])); // Transform // the list // to array } else { list.add(new Element[0]); // Insert an empty Element array. } } } else { list.add(prop.getAttribute("value")); } } /** * Adds an element to the {@link ManifestMetadataParser#m_elements} list. * @param elem the the element to add */ private void addElement(Element elem) { if (m_elements == null) { m_elements = new Element[] { elem }; } else { Element[] newElementsList = new Element[m_elements.length + 1]; System.arraycopy(m_elements, 0, newElementsList, 0, m_elements.length); newElementsList[m_elements.length] = elem; m_elements = newElementsList; } } /** * Removes an element from the {@link ManifestMetadataParser#m_elements} list. * @return an element to remove */ private Element removeLastElement() { int idx = -1; idx = m_elements.length - 1; Element last = m_elements[idx]; if (idx >= 0) { if ((m_elements.length - 1) == 0) { // It is the last element of the list; m_elements = new Element[0]; } else { // Remove the last element of the list : Element[] newElementsList = new Element[m_elements.length - 1]; System.arraycopy(m_elements, 0, newElementsList, 0, idx); m_elements = newElementsList; } } return last; } /** * Looks for the <code>iPOJO-Components</code> header * in the given dictionary. Then, initializes the * {@link ManifestMetadataParser#m_elements} list (adds the * <code>iPOJO</code> root element) and parses the contained * component type declarations and instance configurations. * @param dict the given headers of the manifest file * @throws ParseException if any error occurs */ public void parse(Dictionary dict) throws ParseException { String componentClassesStr = (String) dict.get("iPOJO-Components"); // Add the ipojo element inside the element list addElement(new Element("iPOJO", "")); parseElements(componentClassesStr.trim()); } /** * Parses the given header, initialized the * {@link ManifestMetadataParser#m_elements} list * (adds the <code>iPOJO</code> element) and parses * contained component type declarations and instance configurations. * @param header the given header of the manifest file * @throws ParseException if any error occurs */ public void parseHeader(String header) throws ParseException { // Add the ipojo element inside the element list addElement(new Element("iPOJO", "")); parseElements(header.trim()); } /** * Parses the metadata from the string given in argument. * This methods creates a new {@link ManifestMetadataParser} object * and calls the {@link ManifestMetadataParser#parseElements(String)} * method. The parsing must result as a tree (only one root element). * @param metadata the metadata to parse * @return Element the root element resulting of the parsing * @throws ParseException if any error occurs */ public static Element parse(String metadata) throws ParseException { ManifestMetadataParser parser = new ManifestMetadataParser(); parser.parseElements(metadata); if (parser.m_elements.length != 1) { throw new ParseException("Error in parsing, root element not found : " + metadata); } return parser.m_elements[0]; } /** * Parses the metadata from the given header string. * This method creates a new {@link ManifestMetadataParser} object and then * creates the <code>iPOJO</code> root element, parses content elements * (component types and instances declarations), and returns the resulting * {@link Element} / {@link Attribute} structure. The parsed string * must be a tree (only one root element). * @param header the header to parse * @return Element the root element resulting of the parsing * @throws ParseException if any error occurs */ public static Element parseHeaderMetadata(String header) throws ParseException { ManifestMetadataParser parser = new ManifestMetadataParser(); parser.addElement(new Element("iPOJO", "")); parser.parseElements(header); if (parser.m_elements.length != 1) { throw new ParseException("Error in parsing, root element not found : " + header); } return parser.m_elements[0]; } /** * Parses the given string. * This methods populates the {@link ManifestMetadataParser#m_elements} * list. * @param elems the string to parse */ private void parseElements(String elems) { char[] string = elems.toCharArray(); for (int i = 0; i < string.length; i++) { char current = string[i]; switch (current) { //NOPMD // Beginning of an attribute. case '$': StringBuffer attName = new StringBuffer(); StringBuffer attValue = new StringBuffer(); StringBuffer attNs = null; i++; current = string[i]; // Increment and get the new current char. while (current != '=') { if (current == ':') { attNs = attName; attName = new StringBuffer(); } else { attName.append(current); } i++; current = string[i]; } i = i + 2; // skip =" current = string[i]; while (current != '"') { attValue.append(current); i++; current = string[i]; // Increment and get the new current char. } i++; // skip " current = string[i]; Attribute att = null; if (attNs == null) { att = new Attribute(attName.toString(), attValue.toString()); } else { att = new Attribute(attName.toString(), attNs.toString(), attValue.toString()); } m_elements[m_elements.length - 1].addAttribute(att); break; // End of an element case '}': Element lastElement = removeLastElement(); if (m_elements.length == 0) { addElement(lastElement); } else { Element newQueue = m_elements[m_elements.length - 1]; newQueue.addElement(lastElement); } break; // Space case ' ': break; // do nothing; // Default case default: StringBuffer qname = new StringBuffer(); current = string[i]; while (current != ' ') { qname.append(current); i++; current = string[i]; // Increment and get the new current char. } // Skip spaces while (string[i] == ' ') { i = i + 1; } i = i + 1; // skip { Element elem = null; // Parse the qname String n = qname.toString(); if (n.indexOf(':') == -1) { // No namespace elem = new Element(n, null); } else { // The namespace ends on the first ':' int last = n.lastIndexOf(':'); String ns = n.substring(0, last); String name = n.substring(last + 1); elem = new Element(name.toString(), ns.toString()); } addElement(elem); break; } } } }