/******************************************************************************* * Copyright (c) 2010-2011 Composent, Inc. and others. All rights reserved. This * program and the accompanying materials are made available under the terms of * the Eclipse Public License v1.0 which accompanies this distribution, and is * available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Composent, Inc. - initial API and implementation ******************************************************************************/ package org.eclipse.ecf.internal.osgi.services.remoteserviceadmin; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; public class EndpointDescriptionParser { private static List<String> multiValueTypes; static { multiValueTypes = Arrays.asList(new String[] { "String", "Long", //$NON-NLS-1$ //$NON-NLS-2$ "long", "Double", "double", "float", "Float", "int", "Integer", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ "byte", "Byte", "char", "Character", "boolean", "Boolean", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ "short", "Short" }); //$NON-NLS-1$ //$NON-NLS-2$ } private static final String ENDPOINT_DESCRIPTIONS = "endpoint-descriptions"; //$NON-NLS-1$ private static final String ENDPOINT_DESCRIPTION = "endpoint-description"; //$NON-NLS-1$ private static final String ENDPOINT_PROPERTY = "property"; //$NON-NLS-1$ private static final String ENDPOINT_PROPERTY_NAME = "name"; //$NON-NLS-1$ private static final String ENDPOINT_PROPERTY_VALUE = "value"; //$NON-NLS-1$ private static final String ENDPOINT_PROPERTY_VALUETYPE = "value-type"; //$NON-NLS-1$ private static final String ENDPOINT_PROPERTY_ARRAY = "array"; //$NON-NLS-1$ private static final String ENDPOINT_PROPERTY_LIST = "list"; //$NON-NLS-1$ private static final String ENDPOINT_PROPERTY_SET = "set"; //$NON-NLS-1$ private static final String ENDPOINT_PROPERTY_XML = "xml"; //$NON-NLS-1$ public static String[] noAttributes = new String[0]; private XMLReader xmlReader; class IgnoringHandler extends AbstractHandler { public IgnoringHandler(AbstractHandler parent) { super(parent); this.elementHandled = "IgnoringAll"; //$NON-NLS-1$ } public void startElement(String name, Attributes attributes) { noSubElements(name, attributes); } } /** * Abstract base class for content handlers */ abstract class AbstractHandler extends DefaultHandler { protected ContentHandler parentHandler = null; protected String elementHandled = null; protected StringBuffer characters = null; // character data inside an // element public AbstractHandler() { // Empty constructor for a root handler } public AbstractHandler(ContentHandler parentHandler) { this.parentHandler = parentHandler; xmlReader.setContentHandler(this); } public AbstractHandler(ContentHandler parentHandler, String elementHandled) { this.parentHandler = parentHandler; xmlReader.setContentHandler(this); this.elementHandled = elementHandled; } public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { finishCharacters(); String name = makeSimpleName(localName, qName); startElement(name, attributes); } public abstract void startElement(String name, Attributes attributes) throws SAXException; public void invalidElement(String name, Attributes attributes) { unexpectedElement(this, name, attributes); new IgnoringHandler(this); } public void endElement(String namespaceURI, String localName, String qName) { finishCharacters(); finished(); // Restore the parent content handler xmlReader.setContentHandler(parentHandler); } /** * An implementation for startElement when there are no sub-elements */ protected void noSubElements(String name, Attributes attributes) { unexpectedElement(this, name, attributes); // Create a new handler to ignore subsequent nested elements new IgnoringHandler(this); } /* * Save up character data until endElement or nested startElement * * @see org.xml.sax.ContentHandler#characters */ public void characters(char[] chars, int start, int length) { if (this.characters == null) { this.characters = new StringBuffer(); } this.characters.append(chars, start, length); } // Consume the characters accumulated in this.characters. // Called before startElement or endElement private String finishCharacters() { // common case -- no characters or only whitespace if (this.characters == null || this.characters.length() == 0) { return null; } if (allWhiteSpace(this.characters)) { this.characters.setLength(0); return null; } // process the characters try { String trimmedChars = this.characters.toString().trim(); if (trimmedChars.length() == 0) { // this shouldn't happen due to the test for allWhiteSpace // above System.err.println("Unexpected non-whitespace characters: " //$NON-NLS-1$ + trimmedChars); return null; } processCharacters(trimmedChars); return trimmedChars; } finally { this.characters.setLength(0); } } // Method to override in the handler of an element with CDATA. protected void processCharacters(String data) { if (data.length() > 0) unexpectedCharacterData(this, data); } private boolean allWhiteSpace(StringBuffer sb) { int length = sb.length(); for (int i = 0; i < length; i += 1) if (!Character.isWhitespace(sb.charAt(i))) return false; return true; } /** * Called when this element and all elements nested into it have been * handled. */ protected void finished() { // Do nothing by default } /* * A name used to identify the handler. */ public String getName() { return (elementHandled != null ? elementHandled : "NoName"); //$NON-NLS-1$ } /** * Parse the attributes of an element with only required attributes. */ protected String[] parseRequiredAttributes(Attributes attributes, String[] required) { return parseAttributes(attributes, required, noAttributes); } /** * Parse the attributes of an element with a single optional attribute. */ protected String parseOptionalAttribute(Attributes attributes, String name) { return parseAttributes(attributes, noAttributes, new String[] { name })[0]; } /** * Parse the attributes of an element, given the list of required and * optional ones. Return values in same order, null for those not * present. Log warnings for extra attributes or missing required * attributes. */ protected String[] parseAttributes(Attributes attributes, String[] required, String[] optional) { String[] result = new String[required.length + optional.length]; for (int i = 0; i < attributes.getLength(); i += 1) { String name = attributes.getLocalName(i); String value = attributes.getValue(i).trim(); int j; if ((j = indexOf(required, name)) >= 0) result[j] = value; else if ((j = indexOf(optional, name)) >= 0) result[required.length + j] = value; else unexpectedAttribute(elementHandled, name, value); } for (int i = 0; i < required.length; i += 1) checkRequiredAttribute(elementHandled, required[i], result[i]); return result; } } SAXParser getParser() throws ParserConfigurationException, SAXException { Activator a = Activator.getDefault(); if (a == null) return null; SAXParserFactory factory = a.getSAXParserFactory(); if (factory == null) throw new SAXException("Unable to acquire sax parser"); //$NON-NLS-1$ factory.setNamespaceAware(true); factory.setValidating(false); try { factory.setFeature( "http://xml.org/sax/features/string-interning", true); //$NON-NLS-1$ } catch (SAXException se) { // some parsers may not support string interning } SAXParser theParser = factory.newSAXParser(); if (theParser == null) { throw new SAXException("Unable to create sax parser"); //$NON-NLS-1$ } xmlReader = theParser.getXMLReader(); return theParser; } abstract class RootHandler extends AbstractHandler { public RootHandler() { super(); } public void initialize(DocHandler document, String rootName, Attributes attributes) { this.parentHandler = document; this.elementHandled = rootName; handleRootAttributes(attributes); } protected abstract void handleRootAttributes(Attributes attributes); } class DocHandler extends AbstractHandler { RootHandler rootHandler; public DocHandler(String rootName, RootHandler rootHandler) { super(null, rootName); this.rootHandler = rootHandler; } public void startElement(String name, Attributes attributes) { if (name.equals(elementHandled)) { rootHandler.initialize(this, name, attributes); xmlReader.setContentHandler(rootHandler); } else noSubElements(name, attributes); } } class EndpointDescriptionDocHandler extends DocHandler { public EndpointDescriptionDocHandler(String rootName, RootHandler rootHandler) { super(rootName, rootHandler); } public void processingInstruction(String target, String data) throws SAXException { // do nothing } } class EndpointDescriptionsHandler extends RootHandler { private List<EndpointDescription> endpointDescriptions = new ArrayList<EndpointDescription>(); protected void handleRootAttributes(Attributes attributes) { } public void startElement(String name, Attributes attributes) throws SAXException { if (ENDPOINT_DESCRIPTION.equals(name)) new EndpointDescriptionHandler(this, attributes, endpointDescriptions); else invalidElement(name, attributes); } public void endElement(String namespaceURI, String localName, String qName) { if (elementHandled.equals(localName)) super.endElement(namespaceURI, localName, qName); } public List<EndpointDescription> getEndpointDescriptions() { return endpointDescriptions; } } class EndpointDescriptionHandler extends AbstractHandler { private Map<String, Object> properties; private List<EndpointDescription> descriptions; public EndpointDescriptionHandler(ContentHandler parentHandler, Attributes attributes, List<EndpointDescription> descriptions) { super(parentHandler, ENDPOINT_DESCRIPTION); this.properties = new TreeMap<String, Object>( String.CASE_INSENSITIVE_ORDER); this.descriptions = descriptions; } public void startElement(String name, Attributes attributes) throws SAXException { if (ENDPOINT_PROPERTY.equals(name)) new EndpointPropertyHandler(this, attributes, properties); } public void endElement(String namespaceURI, String localName, String qName) { if (elementHandled.equals(localName)) { descriptions.add(new EndpointDescription(properties)); super.endElement(namespaceURI, localName, qName); } } } private Object createValue(String valueType, String value) { if (value == null) return null; if (valueType.equals("String")) { //$NON-NLS-1$ return value; } else if (valueType.equals("long") || valueType.equals("Long")) { //$NON-NLS-1$ //$NON-NLS-2$ return Long.valueOf(value); } else if (valueType.equals("double") || valueType.equals("Double")) { //$NON-NLS-1$ //$NON-NLS-2$ return Double.valueOf(value); } else if (valueType.equals("float") || valueType.equals("Float")) { //$NON-NLS-1$ //$NON-NLS-2$ return Float.valueOf(value); } else if (valueType.equals("int") || valueType.equals("Integer")) { //$NON-NLS-1$ //$NON-NLS-2$ return Integer.valueOf(value); } else if (valueType.equals("byte") || valueType.equals("Byte")) { //$NON-NLS-1$ //$NON-NLS-2$ return Byte.valueOf(value); } else if (valueType.equals("char") //$NON-NLS-1$ || valueType.equals("Character")) { //$NON-NLS-1$ char[] chars = new char[1]; value.getChars(0, 1, chars, 0); return Character.valueOf(chars[0]); } else if (valueType.equals("boolean") //$NON-NLS-1$ || valueType.equals("Boolean")) { //$NON-NLS-1$ return Boolean.valueOf(value); } else if (valueType.equals("short") || valueType.equals("Short")) { //$NON-NLS-1$ //$NON-NLS-2$ return Short.valueOf(value); } return null; } abstract class MultiValueHandler extends AbstractHandler { protected String valueType; public MultiValueHandler(ContentHandler parentHandler, String elementHandled, String valueType) { super(parentHandler, elementHandled); this.valueType = valueType; } public void startElement(String name, Attributes attributes) throws SAXException { if (ENDPOINT_PROPERTY_VALUE.equals(name)) characters = new StringBuffer(); } public void endElement(String namespaceURI, String localName, String qName) { if (ENDPOINT_PROPERTY_VALUE.equals(localName)) { Object value = createValue( valueType, processValue((characters == null) ? null : characters .toString())); if (value != null) addValue(value); characters = null; } else if (elementHandled.equals(localName)) super.endElement(namespaceURI, localName, qName); } private String processValue(String characters) { if (characters == null || characters.length() == 0) return null; if (valueType.equals("String")) //$NON-NLS-1$ return characters; return characters.trim(); } protected abstract void addValue(Object value); public abstract Object getValues(); } class ArrayMultiValueHandler extends MultiValueHandler { private List<Object> values = new ArrayList<Object>(); public ArrayMultiValueHandler(ContentHandler parentHandler, String elementHandled, String valueType) { super(parentHandler, elementHandled, valueType); } protected Object[] createEmptyArrayOfType() { if (valueType.equals("String")) //$NON-NLS-1$ return new String[] {}; else if (valueType.equals("long") || valueType.equals("Long")) //$NON-NLS-1$ //$NON-NLS-2$ return new Long[] {}; else if (valueType.equals("double") || valueType.equals("Double")) //$NON-NLS-1$ //$NON-NLS-2$ return new Double[] {}; else if (valueType.equals("float") || valueType.equals("Float")) //$NON-NLS-1$ //$NON-NLS-2$ return new Double[] {}; else if (valueType.equals("int") || valueType.equals("Integer")) //$NON-NLS-1$ //$NON-NLS-2$ return new Integer[] {}; else if (valueType.equals("byte") || valueType.equals("Byte")) //$NON-NLS-1$ //$NON-NLS-2$ return new Byte[] {}; else if (valueType.equals("char") //$NON-NLS-1$ || valueType.equals("Character")) //$NON-NLS-1$ return new Character[] {}; else if (valueType.equals("boolean") //$NON-NLS-1$ || valueType.equals("Boolean")) //$NON-NLS-1$ return new Boolean[] {}; else if (valueType.equals("short") || valueType.equals("Short")) //$NON-NLS-1$ //$NON-NLS-2$ return new Short[] {}; else return null; } public Object getValues() { return values.toArray(createEmptyArrayOfType()); } protected void addValue(Object value) { values.add(value); } } class ListMultiValueHandler extends MultiValueHandler { private List<Object> values = new ArrayList<Object>(); public ListMultiValueHandler(ContentHandler parentHandler, String elementHandled, String valueType) { super(parentHandler, elementHandled, valueType); } public Object getValues() { return values; } protected void addValue(Object value) { values.add(value); } } class SetMultiValueHandler extends MultiValueHandler { private Set<Object> values = new HashSet<Object>(); public SetMultiValueHandler(ContentHandler parentHandler, String elementHandled, String valueType) { super(parentHandler, elementHandled, valueType); } public Object getValues() { return values; } protected void addValue(Object value) { values.add(value); } } class XMLValueHandler extends AbstractHandler { private Map<String, String> nsPrefixMap = new HashMap<String, String>(); private StringBuffer buf; public XMLValueHandler(ContentHandler parentHandler) { super(parentHandler, ENDPOINT_PROPERTY_XML); buf = new StringBuffer(); } public void startPrefixMapping(String prefix, String uri) throws SAXException { nsPrefixMap.put(uri, prefix); } public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { buf.append("<").append(qName); //$NON-NLS-1$ for (Iterator<String> i = nsPrefixMap.keySet().iterator(); i .hasNext();) { String nsURI = (String) i.next(); String prefix = (String) nsPrefixMap.get(nsURI); i.remove(); if (nsURI != null) { buf.append(" xmlns"); //$NON-NLS-1$ if (prefix != null) buf.append(":").append(prefix); //$NON-NLS-1$ buf.append("=\"").append(nsURI).append("\""); //$NON-NLS-1$ //$NON-NLS-2$ } } for (int i = 0; i < attributes.getLength(); i++) { buf.append(" "); //$NON-NLS-1$ buf.append(attributes.getQName(i)) .append("=\"").append(attributes.getValue(i)).append("\""); //$NON-NLS-1$ //$NON-NLS-2$ } buf.append(">"); //$NON-NLS-1$ characters = new StringBuffer(); } public void startElement(String name, Attributes attributes) throws SAXException { // not used } public void endElement(String namespaceURI, String localName, String qName) { if (elementHandled.equals(localName)) { super.endElement(namespaceURI, localName, qName); } else { if (characters != null) buf.append(characters); buf.append("</").append(qName).append(">"); //$NON-NLS-1$ //$NON-NLS-2$ characters = null; } } public String getXML() { return buf.toString(); } } class EndpointPropertyHandler extends AbstractHandler { private Map<String, Object> properties; private String name; private String valueType = "String"; //$NON-NLS-1$ private Object value; private MultiValueHandler multiValueHandler; private XMLValueHandler xmlValueHandler; public EndpointPropertyHandler(ContentHandler parentHandler, Attributes attributes, Map<String, Object> properties) throws SAXException { super(parentHandler, ENDPOINT_PROPERTY); name = parseRequiredAttributes(attributes, new String[] { ENDPOINT_PROPERTY_NAME })[0]; String strValue = parseOptionalAttribute(attributes, ENDPOINT_PROPERTY_VALUE); String vt = parseOptionalAttribute(attributes, ENDPOINT_PROPERTY_VALUETYPE); if (vt != null) { if (!multiValueTypes.contains(vt)) throw new SAXException("property element valueType=" + vt //$NON-NLS-1$ + " not allowed"); //$NON-NLS-1$ this.valueType = vt; } this.properties = properties; if (strValue != null) { value = createValue(this.valueType, strValue); if (isValidProperty(name, value)) this.properties.put(name, value); } } public void startElement(String name, Attributes attributes) throws SAXException { // Should not happen if value is non-null if (value != null) throw new SAXException( "property element has both value attribute and sub-element"); //$NON-NLS-1$ if (ENDPOINT_PROPERTY_ARRAY.equals(name)) { if (multiValueHandler == null) multiValueHandler = new ArrayMultiValueHandler(this, ENDPOINT_PROPERTY_ARRAY, valueType); else duplicateElement(this, name, attributes); } else if (ENDPOINT_PROPERTY_LIST.equals(name)) { if (multiValueHandler == null) multiValueHandler = new ListMultiValueHandler(this, ENDPOINT_PROPERTY_LIST, valueType); else duplicateElement(this, name, attributes); } else if (ENDPOINT_PROPERTY_SET.equals(name)) { if (multiValueHandler == null) multiValueHandler = new SetMultiValueHandler(this, ENDPOINT_PROPERTY_SET, valueType); else duplicateElement(this, name, attributes); } else if (ENDPOINT_PROPERTY_XML.equals(name)) { // xml if (xmlValueHandler == null) xmlValueHandler = new XMLValueHandler(this); else duplicateElement(this, name, attributes); } else invalidElement(name, attributes); } public void endElement(String namespaceURI, String localName, String qName) { if (elementHandled.equals(localName)) { if (multiValueHandler != null) { properties.put(name, multiValueHandler.getValues()); multiValueHandler = null; } else if (xmlValueHandler != null) { properties.put(name, xmlValueHandler.getXML()); xmlValueHandler = null; } super.endElement(namespaceURI, localName, qName); } } private boolean isValidProperty(String name, Object value) { return (name != null && value != null); } } public class EndpointDescription { private Map<String, Object> properties; public EndpointDescription(Map<String, Object> properties) { this.properties = properties; } public Map<String, Object> getProperties() { return properties; } public String toString() { StringBuilder builder = new StringBuilder(); builder.append("EndpointDescription [properties="); //$NON-NLS-1$ builder.append(properties); builder.append("]"); //$NON-NLS-1$ return builder.toString(); } } public synchronized void parse(InputStream input) throws IOException { try { getParser(); EndpointDescriptionsHandler endpointDescriptionsHandler = new EndpointDescriptionsHandler(); xmlReader.setContentHandler(new EndpointDescriptionDocHandler( ENDPOINT_DESCRIPTIONS, endpointDescriptionsHandler)); xmlReader.parse(new InputSource(input)); endpointDescriptions = endpointDescriptionsHandler .getEndpointDescriptions(); } catch (SAXException e) { throw new IOException(e.getMessage()); } catch (ParserConfigurationException e) { throw new IOException(e.getMessage()); } finally { input.close(); } } public static String makeSimpleName(String localName, String qualifiedName) { if (localName != null && localName.length() > 0) return localName; int nameSpaceIndex = qualifiedName.indexOf(":"); //$NON-NLS-1$ return (nameSpaceIndex == -1 ? qualifiedName : qualifiedName .substring(nameSpaceIndex + 1)); } public void unexpectedElement(AbstractHandler handler, String element, Attributes attributes) { } public void unexpectedCharacterData(AbstractHandler handler, String cdata) { } public void unexpectedAttribute(String element, String attribute, String value) { } static int indexOf(String[] array, String value) { for (int i = 0; i < array.length; i += 1) { if (value == null ? array[i] == null : value.equals(array[i])) { return i; } } return -1; } public void checkRequiredAttribute(String element, String name, Object value) { } public void duplicateElement(AbstractHandler handler, String element, Attributes attributes) { // ignore the duplicate element entirely because we have already logged // it new IgnoringHandler(handler); } private List<EndpointDescription> endpointDescriptions; public List<EndpointDescription> getEndpointDescriptions() { return endpointDescriptions; } }