/**
* <copyright>
*
* Copyright (c) 2009, 2010 Springsite BV (The Netherlands) 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:
* Martin Taal - Initial API and implementation
*
* </copyright>
*
* $Id: ModelXMLSaver.java,v 1.15 2011/09/14 15:35:53 mtaal Exp $
*/
package org.eclipse.emf.texo.xml;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.xmi.XMIResource;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.impl.ElementHandlerImpl;
import org.eclipse.emf.ecore.xml.type.AnyType;
import org.eclipse.emf.ecore.xml.type.XMLTypeFactory;
import org.eclipse.emf.texo.component.ComponentProvider;
import org.eclipse.emf.texo.component.TexoComponent;
import org.eclipse.emf.texo.converter.ModelEMFConverter;
import org.eclipse.emf.texo.model.ModelObject;
import org.eclipse.emf.texo.model.ModelResolver;
import org.eclipse.emf.texo.provider.IdProvider;
import org.eclipse.emf.texo.provider.TitleProvider;
import org.eclipse.emf.texo.utils.ModelUtils;
import org.eclipse.emf.texo.xml.model.texoextensions.TexoExtensionsModelPackage;
/**
* Responsible for writing a set of modelObjects to an outputstream or writer.
*
* The ModelXMLSaver makes use of the standard EMF {@link XMLResource} or {@link XMIResource} (if {@link #isSaveAsXMI}
* is true).
*
* The following options are set as a default (override by calling {@link #setOptions(Map)} with your own options):
*
* XMLResource.OPTION_ENCODING: "UTF-8"
*
* XMLResource.OPTION_EXTENDED_META_DATA: true
*
* XMLResource.OPTION_SCHEMA_LOCATION: true;
*
* XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE: true
*
* XMLResource.OPTION_KEEP_DEFAULT_CONTENT: true
*
* This option settings ensure that the XML corresponds to the XML schema definition.
*
* @author <a href="mtaal@elver.org">Martin Taal</a>
*/
public class ModelXMLSaver implements TexoComponent {
private Writer writer;
private XMLResource xmlResource;
private Map<String, Object> options = new HashMap<String, Object>();
private List<Object> objects;
private ModelEMFConverter modelEMFConverter = ComponentProvider.getInstance().newInstance(ModelEMFConverter.class);
private boolean saveAsXMI = false;
/**
* Makes this class also capable of supporting serializing {@link EObject} instances.
*/
private boolean objectsAreAlreadyEObjects = false;
// output id and title attributes for each entity
private boolean outputExtensionAttributes = false;
/**
* Writes the model objects ({@link #getModelObjects()}) to the writer ( {@link #getWriter()}) using the
* XML/XMIResource ( {@link #getXmlResource()}).
*/
public void write() {
try {
final XMLResource localXMLResource = getXmlResource();
final List<EObject> eObjects;
if (isObjectsAreAlreadyEObjects()) {
eObjects = new ArrayList<EObject>();
for (Object o : getObjects()) {
eObjects.add((EObject) o);
}
} else {
eObjects = getModelEMFConverter().convert(getObjects());
}
// now do a special method to find all objects without container
// which are not
// in the root, they should be added to the root, otherwise they get
// lost
addObjectsToRoot(eObjects);
localXMLResource.getContents().addAll(eObjects);
if (isOutputExtensionAttributes()) {
addExtensionAttributes();
}
if (!isSaveAsXMI()) {
// set default options which ensure that XML
setDefaultOptions(XMLResource.OPTION_ENCODING, "UTF-8"); //$NON-NLS-1$
setDefaultOptions(XMLResource.OPTION_SUPPRESS_DOCUMENT_ROOT, true);
setDefaultOptions(XMLResource.OPTION_EXTENDED_META_DATA, true);
setDefaultOptions(XMLResource.OPTION_SCHEMA_LOCATION, true);
setDefaultOptions(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, true);
setDefaultOptions(XMLResource.OPTION_KEEP_DEFAULT_CONTENT, true);
setDefaultOptions(
XMLResource.OPTION_ELEMENT_HANDLER,
ComponentProvider.getInstance().newInstance(ElementHandlerImpl.class, new Class[] { boolean.class },
new Object[] { false }));
}
setDefaultOptions(XMLResource.OPTION_SAVE_TYPE_INFORMATION, true);
localXMLResource.save(writer, getOptions());
} catch (final IOException e) {
throw new IllegalStateException(e);
}
}
private void addExtensionAttributes() {
for (Object object : modelEMFConverter.getObjectMapping().keySet()) {
if (!ModelResolver.getInstance().isModelEnabled(object)) {
continue;
}
final ModelObject<?> modelObject = ModelResolver.getInstance().getModelObject(object);
final AnyType anyType = XMLTypeFactory.eINSTANCE.createAnyType();
if (IdProvider.getInstance().hasIdEAttribute(object)) {
anyType.getAnyAttribute().add(TexoExtensionsModelPackage.INSTANCE.getDocumentRoot_Id(),
IdProvider.getInstance().getIdAsString(object));
}
final String title = TitleProvider.getInstance().getTitle(object);
anyType.getAnyAttribute().add(TexoExtensionsModelPackage.INSTANCE.getDocumentRoot_Title(), title);
final String type = ModelUtils.getQualifiedNameFromEClass(modelObject.eClass());
anyType.getAnyAttribute().add(TexoExtensionsModelPackage.INSTANCE.getDocumentRoot_Type(), type);
final EObject eObject = modelEMFConverter.getObjectMapping().get(object);
xmlResource.getEObjectToExtensionMap().put(eObject, anyType);
}
}
private void addObjectsToRoot(final List<EObject> rootObjects) {
final HashMap<EObject, EObject> visited = new HashMap<EObject, EObject>();
for (final EObject eObject : new ArrayList<EObject>(rootObjects)) {
visit(eObject, visited, rootObjects);
}
}
private void visit(final EObject eObject, final HashMap<EObject, EObject> visited, final List<EObject> rootObjects) {
if (visited.containsKey(eObject)) {
return;
}
visited.put(eObject, eObject);
if (eObject.eIsProxy()) {
return;
}
if (eObject.eContainer() == null && !rootObjects.contains(eObject)) {
rootObjects.add(eObject);
}
for (final EReference eReference : eObject.eClass().getEAllReferences()) {
if (eReference.isMany()) {
@SuppressWarnings("unchecked")
final List<EObject> list = (List<EObject>) eObject.eGet(eReference);
for (final EObject refEObject : list) {
visit(refEObject, visited, rootObjects);
}
} else {
final EObject refEObject = (EObject) eObject.eGet(eReference);
if (refEObject != null) {
visit(refEObject, visited, rootObjects);
}
}
}
}
protected void setDefaultOptions(final String option, final Object value) {
if (getOptions().get(option) != null) {
return;
}
getOptions().put(option, value);
}
public Writer getWriter() {
return writer;
}
public void setWriter(final Writer writer) {
this.writer = writer;
}
/**
* Returns the {@link XMIResource} or the {@link XMLResource} which is being used. When no xml resource has been set
* explicitly then one is created. The one created is either a {@link ModelXMIResourceImpl} or a
* {@link ModelXMLResourceImpl}. This depends on the setting of the saveAsXMI ({@link #isSaveAsXMI()}) option.
*
* @return the resource which is used to convert the model objects to a writer.
*/
public XMLResource getXmlResource() {
if (xmlResource == null) {
if (saveAsXMI) {
xmlResource = ComponentProvider.getInstance().newInstance(ModelXMIResourceImpl.class);
} else {
xmlResource = ComponentProvider.getInstance().newInstance(ModelXMLResourceImpl.class);
}
}
return xmlResource;
}
public void setXmlResource(final XMLResource xmlResource) {
this.xmlResource = xmlResource;
}
public Map<String, Object> getOptions() {
return options;
}
public void setOptions(final Map<String, Object> options) {
this.options = options;
}
public List<Object> getObjects() {
return objects;
}
public void setObjects(final List<Object> objects) {
this.objects = objects;
}
public ModelEMFConverter getModelEMFConverter() {
return modelEMFConverter;
}
public void setModelEMFConverter(final ModelEMFConverter modelEMFConverter) {
this.modelEMFConverter = modelEMFConverter;
}
public boolean isSaveAsXMI() {
return saveAsXMI;
}
public void setSaveAsXMI(final boolean saveAsXMI) {
this.saveAsXMI = saveAsXMI;
}
public boolean isOutputExtensionAttributes() {
return outputExtensionAttributes;
}
public void setOutputExtensionAttributes(boolean outputExtensionAttributes) {
this.outputExtensionAttributes = outputExtensionAttributes;
}
protected boolean isObjectsAreAlreadyEObjects() {
return objectsAreAlreadyEObjects;
}
public void setObjectsAreAlreadyEObjects(boolean objectsAreAlreadyEObjects) {
this.objectsAreAlreadyEObjects = objectsAreAlreadyEObjects;
}
}