/** * Copyright (c) 2005-2014 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 * Aurelien pupier (Bonitasoft S.A.) - Bug 441484: respect encoding provided in saveToString Method */ package org.eclipse.emf.ecore.xmi.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Writer; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.EcorePackage; import org.eclipse.emf.ecore.impl.EPackageRegistryImpl; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.emf.ecore.util.BasicExtendedMetaData; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.ecore.util.ExtendedMetaData; import org.eclipse.emf.ecore.xmi.DOMHandler; import org.eclipse.emf.ecore.xmi.EcoreBuilder; import org.eclipse.emf.ecore.xmi.XMLResource; import org.eclipse.emf.ecore.xmi.impl.XMLParserPoolImpl; import org.eclipse.emf.ecore.xmi.impl.XMLResourceFactoryImpl; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * XMLProcessor provides helper methods to serialize and deserialize XML using EMF framework. * */ public class XMLProcessor { protected EcoreBuilder ecoreBuilder; protected final static String XML_EXTENSION = "xml"; protected final static String STAR_EXTENSION = "*"; protected final static URI XML_URI = URI.createFileURI(XML_EXTENSION); protected Map<String, Resource.Factory> registrations; protected Map<Object, Object> loadOptions = new HashMap<Object, Object>(); protected Map<Object, Object> saveOptions = new HashMap<Object, Object>(); protected ExtendedMetaData extendedMetaData; protected EPackage.Registry registry; /** * Protected constructor - should be used for in statically generated models, * or in the case schema(s) is known when XMLProcessor is created */ protected XMLProcessor(EPackage.Registry registry) { this.registry = registry; this.extendedMetaData = createExtendedMetaData(); ecoreBuilder = createEcoreBuilder(); loadOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, extendedMetaData); loadOptions.put(XMLResource.OPTION_USE_PARSER_POOL, new XMLParserPoolImpl(true)); loadOptions.put(XMLResource.OPTION_USE_XML_NAME_TO_FEATURE_MAP, new HashMap<String, EStructuralFeature>()); loadOptions.put(XMLResource.OPTION_USE_DEPRECATED_METHODS, Boolean.FALSE); loadOptions.put(XMLResource.OPTION_CONFIGURATION_CACHE, Boolean.TRUE); saveOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, extendedMetaData); saveOptions.put(XMLResource.OPTION_USE_CACHED_LOOKUP_TABLE, new ArrayList<Object>()); // TODO saveOptions.put(XMLResource.OPTION_CONFIGURATION_CACHE, Boolean.TRUE); } /** * Use this constructor to create an XML processor that is not aware of any schema. * Use {@link org.eclipse.emf.ecore.xmi.XMLOptions} to configure this processor to * process, for example, schemaLocation/noNamespaceSchema location attributes. */ public XMLProcessor() { this.extendedMetaData = createExtendedMetaData(); ecoreBuilder = createEcoreBuilder(); // register default options loadOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, extendedMetaData); loadOptions.put(XMLResource.OPTION_USE_PARSER_POOL, new XMLParserPoolImpl(true)); loadOptions.put(XMLResource.OPTION_USE_DEPRECATED_METHODS, Boolean.FALSE); loadOptions.put(XMLResource.OPTION_CONFIGURATION_CACHE, Boolean.TRUE); saveOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, extendedMetaData); saveOptions.put(XMLResource.OPTION_CONFIGURATION_CACHE, Boolean.TRUE); } /* * Create an XML processor that is aware of XML Schema specified. * @param schemaURIs - a URI {@link org.eclipse.common.util.URI} which point to an XML Schema */ public XMLProcessor(URI schemaURI) throws SAXException { this(Collections.singleton(schemaURI)); } /** * Create an XML processor that is aware of XML Schemas specified. * @param schemaURIs - a list of URI {@link URI} which point to XML Schemas * @throws SAXException */ public XMLProcessor(Collection<URI> schemaURIs) throws SAXException { this(new EPackageRegistryImpl()); loadOptions.put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE); loadOptions.put(XMLResource.OPTION_USE_LEXICAL_HANDLER, Boolean.TRUE); saveOptions.put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE); saveOptions.put(XMLResource.OPTION_SCHEMA_LOCATION, Boolean.TRUE); try { for (Resource resource : ecoreBuilder.generate(schemaURIs)) { for (EPackage ePackage : EcoreUtil.<EPackage>getObjectsByType(resource.getContents(), EcorePackage.Literals.EPACKAGE)) { EcoreUtil.freeze(ePackage); } } } catch (InvocationTargetException ie) { throw new SAXException((Exception)ie.getTargetException()); } catch (Exception e) { throw new SAXException(e); } } protected Map<String, Resource.Factory> getRegistrations() { if (registrations == null) { Map<String, Resource.Factory> result = new HashMap<String, Resource.Factory>(); result.put(STAR_EXTENSION, new XMLResourceFactoryImpl()); registrations = result; } return registrations; } /** * @return a map of namespace URI to EPackage. This registry should be treated as read-only. The registry * does not include packages loaded on demand (@see #getExtendedMetaData to retrieve demand loaded packages) */ public EPackage.Registry getEPackageRegistry() { return registry; } /** * @return The ExtendedMetaData used by XMLProcessor to register all Ecore packages. * The ExtendedMetaData can be used to retieve information about the model (e.g. @see ExtendedMetaData#getElement(String, String)). * The ExtendedMetaData should be treated as read-only. */ public ExtendedMetaData getExtendedMetaData() { return extendedMetaData; } /** * Given a system identifier and option, this methods creates an EMF Resource * (using URI#createURI method) and loads the resource data. * @param systemId - system identifier * @param options - options map * @return Loaded resource * @throws IOException * @see org.eclipse.emf.ecore.resource.Resource * @see org.eclipse.emf.common.util.URI */ public Resource load(String systemId, Map<?, ?> options) throws IOException { ResourceSet resourceSet = createResourceSet(); XMLResource resource = (XMLResource)resourceSet.createResource(URI.createURI(systemId)); InputSource inputSource = new InputSource(); inputSource.setSystemId(systemId); if (options != null) { Map<Object, Object> mergedOptions = new HashMap<Object, Object>(loadOptions); mergedOptions.putAll(options); resource.load(inputSource, mergedOptions); } else { resource.load(inputSource, loadOptions); } resourceSet.getPackageRegistry().putAll(registry); return resource; } public Resource load(InputStream is, Map<?, ?> options) throws IOException { ResourceSet resourceSet = createResourceSet(); Resource resource = resourceSet.createResource(XML_URI); if (options != null) { Map<Object, Object> mergedOptions = new HashMap<Object, Object>(loadOptions); mergedOptions.putAll(options); resource.load(is, mergedOptions); } else { resource.load(is, loadOptions); } resourceSet.getPackageRegistry().putAll(registry); return resource; } public Resource load(InputSource inputSource, Map<?, ?> options) throws IOException { ResourceSet resourceSet = createResourceSet(); XMLResource resource = (XMLResource)resourceSet.createResource(XML_URI); if (options != null) { Map<Object, Object> mergedOptions = new HashMap<Object, Object>(loadOptions); mergedOptions.putAll(options); resource.load(inputSource, mergedOptions); } else { resource.load(inputSource, loadOptions); } resourceSet.getPackageRegistry().putAll(registry); return resource; } public Resource load(Node node, Map<?, ?> options) throws IOException { ResourceSet resourceSet = createResourceSet(); XMLResource resource = (XMLResource)resourceSet.createResource(XML_URI); if (options != null) { Map<Object, Object> mergedOptions = new HashMap<Object, Object>(loadOptions); mergedOptions.putAll(options); resource.load(node, mergedOptions); } else { resource.load(node, loadOptions); } resourceSet.getPackageRegistry().putAll(registry); return resource; } public void save(OutputStream outputStream, Resource resource, Map<?, ?> options) throws IOException { if (options != null) { Map<Object, Object> mergedOptions = new HashMap<Object, Object>(saveOptions); mergedOptions.putAll(options); resource.save(outputStream, mergedOptions); } else { resource.save(outputStream, saveOptions); } } public void save(Writer writer, Resource resource, Map<?, ?> options) throws IOException { if (options != null) { Map<Object, Object> mergedOptions = new HashMap<Object, Object>(saveOptions); mergedOptions.putAll(options); ((XMLResource)resource).save(writer, mergedOptions); } else { ((XMLResource)resource).save(writer, saveOptions); } } public void save(Document document, Resource resource, DOMHandler handler, Map<?, ?> options) throws IOException { if (options != null) { Map<Object, Object> mergedOptions = new HashMap<Object, Object>(saveOptions); mergedOptions.putAll(options); ((XMLResource)resource).save(document, mergedOptions, handler); } else { ((XMLResource)resource).save(document, saveOptions, handler); } } public String saveToString(Resource resource, Map<?, ?> options) throws IOException { Map<Object, Object> mergedOptions = new HashMap<Object, Object>(saveOptions); if (options != null) { mergedOptions.putAll(options); } ByteArrayOutputStream os = new ByteArrayOutputStream(); ((XMLResource)resource).save(os, mergedOptions); final Object encoding = mergedOptions.get(XMLResource.OPTION_ENCODING); if (encoding != null && encoding instanceof String) { return os.toString((String) encoding); } else { return os.toString(); } } protected ResourceSet createResourceSet() { ResourceSet resourceSet = new ResourceSetImpl(); resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().putAll(getRegistrations()); return resourceSet; } protected EcoreBuilder createEcoreBuilder() { return new DefaultEcoreBuilder(extendedMetaData); } /** * This methods can be used to provide a different ExtendedMetaData. * Note that if this method creates a new EPackage.Registry it must also assign the global registry * variable. * @return ExtendedMetaData */ protected ExtendedMetaData createExtendedMetaData() { if (registry == null) { registry = new EPackageRegistryImpl(); } return new BasicExtendedMetaData(registry); } }