/**
* <copyright>
*
* 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
*
* </copyright>
*
* $Id: XMLContentHandlerImpl.java,v 1.5 2009/10/20 09:47:00 emerks Exp $
*/
package org.eclipse.emf.ecore.xmi.impl;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
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.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.impl.EPackageRegistryImpl;
import org.eclipse.emf.ecore.resource.ContentHandler;
import org.eclipse.emf.ecore.resource.impl.ContentHandlerImpl;
import org.eclipse.emf.ecore.util.BasicExtendedMetaData;
import org.eclipse.emf.ecore.util.ExtendedMetaData;
import org.eclipse.emf.ecore.util.FeatureMap;
import org.eclipse.emf.ecore.xmi.XMIResource;
import org.eclipse.emf.ecore.xmi.XMLDefaultHandler;
import org.eclipse.emf.ecore.xmi.XMLLoad;
import org.eclipse.emf.ecore.xmi.XMLOptions;
import org.eclipse.emf.ecore.xmi.XMLParserPool;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xml.type.AnyType;
import org.eclipse.emf.ecore.xml.type.XMLTypeDocumentRoot;
import org.eclipse.emf.ecore.xml.type.XMLTypePackage;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
/**
* A {@link ContentHandler} content handler implementation for dealing with XML content.
*/
public class XMLContentHandlerImpl extends ContentHandlerImpl
{
/**
* Returns whether the given name and namespace represents a specialize XMI root element tag.
* @param name the element name to consider.
* @param namespace the element namespace to consider.
* @return whether the given name and namespace represents a specialize XMI root element tag.
*/
public static boolean isXMINameAndNamespace(String name, String namespace)
{
return XMIResource.XMI_TAG_NAME.equals(name) && isXMINamespace(namespace);
}
/**
* Returns whether the given namespace represents an XMI namespace.
* @param namespace the element namespace to consider.
* @return whether the given namespace represents an XMI namespace.
*/
public static boolean isXMINamespace(String namespace)
{
return
namespace != null &&
(namespace.startsWith(XMIResource.XMI_NAMESPACE_PREFIX) ||
namespace.equals(XMIResource.XMI_URI));
}
private static final XMLParserPool XML_PARSER_POOL = new XMLParserPoolImpl(1, true);
private static final Map<Object, Object> DEFAULT_SAVE_OPTIONS;
private static final Map<Object, Object> DEFAULT_LOAD_OPTIONS;
static
{
Map<Object, Object> defaultLoadOptions = new HashMap<Object, Object>();
Map<Object, Object> defaultSaveOptions = new HashMap<Object, Object>();
defaultLoadOptions.put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE);
defaultSaveOptions.put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE);
ExtendedMetaData extendedMetaData = new BasicExtendedMetaData(new EPackageRegistryImpl());
defaultLoadOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, extendedMetaData);
defaultSaveOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, extendedMetaData);
XMLOptions xmlOptions = new XMLOptionsImpl();
xmlOptions.setProcessAnyXML(true);
defaultLoadOptions.put(XMLResource.OPTION_XML_OPTIONS, xmlOptions);
defaultLoadOptions.put(XMLResource.OPTION_USE_PARSER_POOL, XML_PARSER_POOL);
DEFAULT_LOAD_OPTIONS = defaultLoadOptions;
DEFAULT_SAVE_OPTIONS = defaultSaveOptions;
}
/**
* Creates a specialized XML resource for consuming just the root elements
* as well as the first nested element,
* if the root element is an {@link #isXMINameAndNamespace(String, String) XMI root element}.
* @return an XML Resource for the root element and possibly the first nested element.
*/
protected XMLResource createXMLResource()
{
return
new XMLResourceImpl()
{
{
defaultLoadOptions = DEFAULT_LOAD_OPTIONS;
defaultSaveOptions = DEFAULT_SAVE_OPTIONS;
}
@Override
protected XMLLoad createXMLLoad()
{
return
new XMLLoadImpl(createXMLHelper())
{
@Override
public XMLDefaultHandler createDefaultHandler()
{
return
new SAXXMLHandler(resource, helper, options)
{
@Override
public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException
{
super.startElement(uri, localName, name, attributes);
int depth = elements.size();
if (depth == 1 && !isXMINameAndNamespace(localName, uri) ||
depth == 2 && !isXMINamespace(uri))
{
endElement(uri, localName, name);
if (depth == 2)
{
endElement(uri, localName, name);
}
endDocument();
throw new RuntimeException();
}
}
@Override
protected EPackage handleMissingPackage(String uriString)
{
EPackage result = super.handleMissingPackage(uriString);
return
result == XMLTypePackage.eINSTANCE ?
extendedMetaData.demandPackage(uriString) :
result;
}
@Override
protected void processSchemaLocations(String prefix, String name)
{
// Completely ignore schema locations.
}
};
}
};
}
};
}
/**
* Returns the character set of the input stream; it's computed from the encoding specified in the XML content.
* @param uri the URI of the input stream.
* @param inputStream the input stream of the XML content.
* @param options any options that might influence the loading the the XML content.
* @param context a cache for previously computed information.
* @return the character set of the input stream.
* @throws IOException if there is a problem loading the XML content.
*/
protected String getCharset(URI uri, InputStream inputStream, Map<?, ?> options, Map<Object, Object> context) throws IOException
{
String result = (String)context.get(ContentHandler.CHARSET_PROPERTY);
if (result == null)
{
result = load(uri, inputStream, options, context).getEncoding();
context.put(ContentHandler.CHARSET_PROPERTY, result);
}
return result;
}
/**
* Returns the XML resource loaded from the input stream.
* @param uri the URI of the input stream.
* @param inputStream the input stream of the XML content.
* @param options any options that might influence the loading the the XML content.
* @param context a cache for previously computed information.
* @return the XML resource loaded from the input stream.
* @throws IOException if there is a problem loading the XML content.
*/
protected XMLResource load(URI uri, InputStream inputStream, Map<?, ?> options, Map<Object, Object> context) throws IOException
{
XMLResource result = (XMLResource)context.get("XMLResource");
if (result == null)
{
try
{
result = createXMLResource();
result.setURI(uri);
result.load
(new BufferedInputStream(inputStream)
{
@Override
public void close() throws IOException
{
// Ignore close since we don't want to let the parser close the stream when it's done.
}
},
null);
}
catch (Throwable exception)
{
// Ignore since we expect this to fail because of early termination
// and if there is some other kind of failure, then we'll simply not be able to match the content type.
}
finally
{
inputStream.reset();
}
context.put("XMLResource", result);
}
return result;
}
/**
* This implementation augments the base implementation by computing the {@link ContentHandler#CHARSET_PROPERTY character set property}.
*/
@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);
if (isRequestedProperty(ContentHandler.CHARSET_PROPERTY, options))
{
result.put(ContentHandler.CHARSET_PROPERTY, getCharset(uri, inputStream, options, context));
}
return result;
}
public static class XMI extends XMLContentHandlerImpl
{
/**
* Returns a valid content description if the XML content of the input stream has an XMI version attribute.
*/
@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();
if (!rootContents.isEmpty())
{
EObject root = rootContents.get(0);
if (root instanceof AnyType)
{
for (FeatureMap.Entry entry : ((AnyType)root).getAnyAttribute())
{
EStructuralFeature attributeFeature = entry.getEStructuralFeature();
if ("version".equals(ExtendedMetaData.INSTANCE.getName(attributeFeature)) &&
isXMINamespace(ExtendedMetaData.INSTANCE.getNamespace(attributeFeature)))
{
result.put(VALIDITY_PROPERTY, ContentHandler.Validity.VALID);
result.put(CONTENT_TYPE_PROPERTY, "org.eclipse.emf.ecore.xmi");
break;
}
}
}
}
}
}
return result;
}
/**
* A describer that {@link #createContentHandler(Map) creates} an {@link XMI} instance.
*/
public static class Describer extends ContentHandlerImpl.Describer
{
/**
* Creates a {@link RootXMLContentHandlerImpl} instance.
*/
@Override
protected ContentHandler createContentHandler(Map<String, String> parameters)
{
return new XMI();
}
}
}
}