/** * Copyright (c) 2007 IBM Corporation 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: * IBM - Initial API and implementation */ package org.eclipse.emf.ecore.xmi.impl; import java.io.IOException; import java.io.InputStream; import java.util.Map; import java.util.regex.Pattern; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.resource.ContentHandler; import org.eclipse.emf.ecore.resource.impl.ContentHandlerImpl; import org.eclipse.emf.ecore.util.ExtendedMetaData; import org.eclipse.emf.ecore.xmi.XMLResource; import org.eclipse.emf.ecore.xml.type.XMLTypeDocumentRoot; import org.eclipse.emf.ecore.xml.type.XMLTypePackage; /** * A specialized {@link ContentHandler} implementation that matches root element names and namespaces. */ public class RootXMLContentHandlerImpl extends XMLContentHandlerImpl { /** * The content type identifier for content that matches this handler. */ protected String contentTypeID; /** * The file extensions for which this handler applies. */ protected String [] extensions; /** * The kind of content that's expected; {@link #XMI_KIND XMI content} has special handling. */ protected String kind; /** * The namespace of the root element or the first non-XMI element. */ protected String namespace; /** * The namespace pattern of the root element or the first non-XMI element. */ protected Pattern namespacePattern; /** * The expected name of the root element or the first non-XMI element. */ protected String [] elementNames; /** * The key in the {@link #RootXMLContentHandlerImpl(Map) constructor}'s parameter map representing the content type identifier String. * @see #contentTypeID */ public static final String CONTENT_TYPE_ID = "contentTypeID"; /** * The key in the {@link #RootXMLContentHandlerImpl(Map) constructor}'s parameter map representing the extensions * which are encoded as a space separate list of suffixes. * @see #extensions */ public static final String EXTENSIONS = "extensions"; /** * The key in the {@link #RootXMLContentHandlerImpl(Map) constructor}'s parameter map representing the kind of resource being processed; * only the value <code>xmi<code> is recognized. * @see #kind */ public static final String KIND = "kind"; /** * A {@link #kind} value indicating XMI content. */ public static final String XMI_KIND = "xmi"; /** * The key in the {@link #RootXMLContentHandlerImpl(Map) constructor}'s parameter map representing the namespace. * @see #namespace */ public static final String NAMESPACE = "namespace"; /** * The key in the {@link #RootXMLContentHandlerImpl(Map) constructor}'s parameter map representing the namespace pattern. * @see #namespacePattern */ public static final String NAMESPACE_PATTERN = "namespacePattern"; /** * The key in the {@link #RootXMLContentHandlerImpl(Map) constructor}'s parameter map representing the root element names * which are encoded as a space separated list of names. * @see #elementNames */ public static final String ELEMENT_NAMES = "elementNames"; /** * Creates an instance corresponding to the given parameters. * @param parameters the map of key value pairs. * @see #CONTENT_TYPE_ID * @see #EXTENSIONS * @see #KIND * @see #NAMESPACE * @see #ELEMENT_NAMES */ public RootXMLContentHandlerImpl(Map<String, String> parameters) { this (parameters.get(CONTENT_TYPE_ID), parameters.containsKey(EXTENSIONS) ? parameters.get(EXTENSIONS).split(" ") : new String [0], parameters.get(KIND), parameters.get(NAMESPACE), parameters.get(NAMESPACE_PATTERN) != null ? Pattern.compile(parameters.get(NAMESPACE_PATTERN)) : null, parameters.containsKey(ELEMENT_NAMES) ? parameters.get(ELEMENT_NAMES).split(" ") : new String [0]); } /** * Creates an instance initialized with the given arguments. * @param contentTypeID the content type identifier for content recognized this this handler. * @param extensions the {@link URI#fileExtension() extensions} handled by this instance; * <code>null</code> or an empty array specifies that this handler applies for all URIs. * @param kind the kind of content that's expected; {@link #XMI_KIND} supports special handling. * @param namespace the expected namespace of the root element or first non-XMI element. * @param elementNames the expected element names of the root element or the first non-XMI element; * <code>null</code> or an empty array specifies that any root element name is allowed. */ public RootXMLContentHandlerImpl (String contentTypeID, String [] extensions, String kind, String namespace, String [] elementNames) { this(contentTypeID, extensions, kind, namespace, null, elementNames); } /** * Creates an instance initialized with the given arguments. * @param contentTypeID the content type identifier for content recognized this this handler. * @param extensions the {@link URI#fileExtension() extensions} handled by this instance; * <code>null</code> or an empty array specifies that this handler applies for all URIs. * @param kind the kind of content that's expected; {@link #XMI_KIND} supports special handling. * @param namespacePattern a pattern that will match the expected namespace of the root element or first non-XMI element. * @param elementNames the expected element names of the root element or the first non-XMI element; * <code>null</code> or an empty array specifies that any root element name is allowed. */ public RootXMLContentHandlerImpl (String contentTypeID, String [] extensions, String kind, Pattern namespacePattern, String [] elementNames) { this(contentTypeID, extensions, kind, null, namespacePattern, elementNames); } private RootXMLContentHandlerImpl (String contentTypeID, String [] extensions, String kind, String namespace, Pattern namespacePattern, String [] elementNames) { assert namespacePattern == null || namespace == null; this.contentTypeID = contentTypeID; this.extensions = extensions; this.kind = kind; this.namespace = namespace; this.namespacePattern = namespacePattern; this.elementNames = elementNames; } /** * Returns true if the {@link #extensions} are null or empty, of if the URI's {@link URI#fileExtension() file extension} matches one of the extension's values. */ @Override public boolean canHandle(URI uri) { if (extensions == null || extensions.length == 0) { return true; } else { String fileExtension = uri.fileExtension(); if (fileExtension != null) { for (String extension : extensions) { if (fileExtension.equals(extension)) { return true; } } } return false; } } /** * Returns a valid content description if the XML content of the input stream has an element that matches the element names and namespace. */ @Override public Map<String, Object> contentDescription(URI uri, InputStream inputStream, Map<?, ?> options, Map<Object, Object> context) throws IOException { Map<String, Object> result = super.contentDescription(uri, inputStream, options, context); XMLResource xmlResource = load(uri, inputStream, options, context); EList<EObject> contents = xmlResource.getContents(); if (!contents.isEmpty()) { EObject eObject = contents.get(0); if (eObject instanceof XMLTypeDocumentRoot) { XMLTypeDocumentRoot documentRoot = (XMLTypeDocumentRoot)eObject; EList<EObject> rootContents = documentRoot.eContents(); String rootElementName = null; String rootElementNamespace = null; for (EObject root : rootContents) { EReference eContainmentFeature = root.eContainmentFeature(); if (eContainmentFeature.getEContainingClass() != XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT) { rootElementName = eContainmentFeature.getName(); rootElementNamespace = ExtendedMetaData.INSTANCE.getNamespace(eContainmentFeature); if (XMI_KIND.equals(kind) && isXMINameAndNamespace(rootElementName, rootElementNamespace)) { // Look for the first non-XMI element. // for (EObject candidate : root.eContents()) { eContainmentFeature = candidate.eContainmentFeature(); if (eContainmentFeature.getEContainingClass() != XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT) { rootElementNamespace = ExtendedMetaData.INSTANCE.getNamespace(eContainmentFeature); if (!isXMINamespace(rootElementNamespace)) { rootElementName = eContainmentFeature.getName(); break; } } } } break; } } if (rootElementName != null && isMatchingNamespace(rootElementNamespace)) { boolean elementNameMatched = false; if (elementNames == null || elementNames.length == 0) { elementNameMatched = true; } else { for (String elementName : elementNames) { if (rootElementName.equals(elementName)) { elementNameMatched = true; break; } } } if (elementNameMatched) { result.put(VALIDITY_PROPERTY, ContentHandler.Validity.VALID); } } result.put(CONTENT_TYPE_PROPERTY, contentTypeID == null ? rootElementNamespace == null ? "" : rootElementNamespace : contentTypeID); return result; } } result.put(CONTENT_TYPE_PROPERTY, contentTypeID == null ? "" : contentTypeID); return result; } /** * Returns whether the given root element's namespace matches the namespace expected for the content type. */ protected boolean isMatchingNamespace(String rootElementNamespace) { if (namespacePattern != null) { return namespacePattern.matcher(rootElementNamespace == null ? "" : rootElementNamespace).matches(); } else { return namespace == null ? rootElementNamespace == null : namespace.equals(rootElementNamespace); } } /** * A describer that {@link #createContentHandler(Map) creates} a {@link RootXMLContentHandlerImpl} instance. */ public static class Describer extends ContentHandlerImpl.Describer { /** * Creates a {@link RootXMLContentHandlerImpl} instance. */ @Override protected ContentHandler createContentHandler(Map<String, String> parameters) { return new RootXMLContentHandlerImpl(parameters); } } }