/**
* <copyright>
*
* Copyright (c) 2005, 2006, 2007, 2008 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
* </copyright>
*
* $Id: XmlPersistenceContentHandler.java,v 1.7 2008/06/02 07:15:29 mtaal Exp $
*/
package org.eclipse.emf.teneo.annotations.xml;
import java.io.InputStream;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEClass;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEDataType;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEPackage;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedEStructuralFeature;
import org.eclipse.emf.teneo.annotations.pamodel.PAnnotatedModel;
import org.eclipse.emf.teneo.annotations.pannotation.PAnnotation;
import org.eclipse.emf.teneo.extension.ExtensionManager;
import org.eclipse.emf.teneo.extension.ExtensionManagerAware;
import org.eclipse.emf.teneo.extension.ExtensionPoint;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
* SAX ContentHandler for processing XML persistence mapping. Used internally by
* {@link XmlPersistenceMapper}.
*/
public class XmlPersistenceContentHandler extends DefaultHandler implements ExtensionPoint, ExtensionManagerAware {
// Parse states
// Document root.
private static final int ROOT = 0;
// <persistence-mapping>
private static final int PERSISTENCE_MAPPING = 1;
// <epackage>
private static final int EPACKAGE = 2;
// Annotation element for an <epackage>.
private static final int EPACKAGE_ANNOTATION = 3;
// <eclass>
private static final int ECLASS = 4;
// Annotation element for an <eclass>.
private static final int ECLASS_ANNOTATION = 5;
// <eattribute>, <ereference> or <property>.
private static final int ESTRUCTURALFEATURE = 6;
// Annotation element for an <eattribute>, <ereference> or <property>.
private static final int ESTRUCTURALFEATURE_ANNOTATION = 7;
// Annotation element inside another annotation.
private static final int NESTED_ANNOTATION = 8;
// Value for an annotation element.
private static final int ANNOTATION_ATTRIBUTE = 9;
// <eclass>
private static final int EDATATYPE = 10;
// Annotation element for an <eclass>.
private static final int EDATATYPE_ANNOTATION = 11;
// The pattern to split the XML element names against.
private static Pattern XML_NAME_PATTERN = Pattern.compile("-");
private static String convertXmlNameToEStructuralFeatureName(String xmlName) {
final String[] elementNameParts = XML_NAME_PATTERN.split(xmlName);
final StringBuffer featureName = new StringBuffer();
for (int i = 0; i < elementNameParts.length; i++) {
String part = elementNameParts[i];
if (i > 0) {
part = part.substring(0, 1).toUpperCase() + part.substring(1);
}
featureName.append(part);
}
return featureName.toString();
}
// The PAnnotatedModel that will be populated.
private PAnnotatedModel pAnnotatedModel;
// The PAnnotatedEPackage to which the XML annotations will be applied.
private PAnnotatedEPackage pAnnotatedEPackage;
// The current PAnnotatedEClass.
private PAnnotatedEClass pAnnotatedEClass;
// The current PAnnotatedEDataType.
private PAnnotatedEDataType pAnnotatedEDataType;
// The current PAnnotatedEStructuralFeature of pAnnotatedEClass.
private PAnnotatedEStructuralFeature pAnnotatedEStructuralFeature;
// Stack of PAnnotations.
private Stack<PAnnotation> pAnnotations = new Stack<PAnnotation>();
// The current EAttribute of the current pAnnotation. Used only for EDataTypes.
private EAttribute pAnnotationEAttribute;
// Stack of parse states.
private Stack<Integer> parseStates = new Stack<Integer>();
// prefix for extra efeature parsing
private String prefix;
// ExtensionManager
private ExtensionManager extensionManager;
/** The xml element to structural feature mapper */
private XmlElementToEStructuralFeatureMapper xmlElementToEStructuralFeatureMapper;
public XmlPersistenceContentHandler() {
parseStates.push(ROOT);
}
/** Set the schema */
public void setSchema(InputStream schema) {
xmlElementToEStructuralFeatureMapper =
getExtensionManager().getExtension(XmlElementToEStructuralFeatureMapper.class);
xmlElementToEStructuralFeatureMapper.parseSchema(schema);
}
/**
* Returns the current parse state.
*/
protected int getParseState() {
assert (parseStates.size() >= 1) : "Parse state stack must contain at least one element.";
return (parseStates.peek()).intValue();
}
protected PAnnotation getPAnnotation() {
return pAnnotations.peek();
}
/**
* Applies an annotation on an EObject based on the given XML element name.
*
*/
@SuppressWarnings("unchecked")
protected void applyAnnotation(EObject pAnnotatedEModelElement, String elementName, Attributes attributes)
throws SAXException {
final EStructuralFeature annotationEStructuralFeature =
getEStructuralFeature(pAnnotatedEModelElement, elementName);
if (annotationEStructuralFeature == null) {
throw new SAXException("Cannot handle element <" + elementName + ">");
}
final PAnnotation pAnnotation =
(PAnnotation) EcoreUtil.create((EClass) annotationEStructuralFeature.getEType());
pAnnotations.push(pAnnotation);
if (annotationEStructuralFeature.isMany()) {
((List) pAnnotatedEModelElement.eGet(annotationEStructuralFeature)).add(pAnnotation);
} else {
pAnnotatedEModelElement.eSet(annotationEStructuralFeature, pAnnotation);
}
// Apply attributes to pAnnotation
for (int i = 0, n = attributes.getLength(); i < n; i++) {
final EAttribute eAttribute = (EAttribute) getEStructuralFeature(pAnnotation, attributes.getLocalName(i));
final EDataType eDataType = eAttribute.getEAttributeType();
final Object valueObject =
eDataType.getEPackage().getEFactoryInstance().createFromString(eDataType, attributes.getValue(i));
if (eAttribute.isMany()) {
((List) pAnnotation.eGet(eAttribute)).add(valueObject);
} else {
pAnnotation.eSet(eAttribute, valueObject);
}
}
}
/**
* Returns an estructuralfeature on the basis of the name, mainly does conversion of the xmlName
* to the efeaturename, the prefix returned from getPrefix is also used. todo: move prefix
* handling to XmlElementToEStructuralFeatureMapper.
*/
protected EStructuralFeature getEStructuralFeature(EObject pAnnotatedEModelElement, String xmlName) {
String annotationEStructuralFeatureName = convertXmlNameToEStructuralFeatureName(xmlName);
EStructuralFeature annotationEStructuralFeature =
pAnnotatedEModelElement.eClass().getEStructuralFeature(annotationEStructuralFeatureName);
if (annotationEStructuralFeature == null) {
annotationEStructuralFeatureName = xmlElementToEStructuralFeatureMapper.getEStructuralFeatureName(xmlName);
annotationEStructuralFeature =
pAnnotatedEModelElement.eClass().getEStructuralFeature(annotationEStructuralFeatureName);
}
// if still null then try with the prefix
if (annotationEStructuralFeature == null) {
// note if a prefix is added then the first character of the first part has to be
// upper-cased
String name = convertXmlNameToEStructuralFeatureName(xmlName);
annotationEStructuralFeatureName = prefix + name.substring(0, 1).toUpperCase() + name.substring(1);
;
annotationEStructuralFeature =
pAnnotatedEModelElement.eClass().getEStructuralFeature(annotationEStructuralFeatureName);
}
return annotationEStructuralFeature;
}
// --------------------------------------------------------------------
// Implementation of ContentHandler interface.
// --------------------------------------------------------------------
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// Change parse state.
int newParseState;
switch (getParseState()) {
case ROOT:
newParseState = PERSISTENCE_MAPPING;
break;
case PERSISTENCE_MAPPING:
assert (localName.equals("epackage"));
newParseState = EPACKAGE;
break;
case EPACKAGE:
if (localName.equals("eclass")) {
newParseState = ECLASS;
} else if (localName.equals("edatatype")) {
newParseState = EDATATYPE;
} else {
newParseState = EPACKAGE_ANNOTATION;
}
break;
case ECLASS:
if (localName.equals("eattribute") || localName.equals("ereference") || localName.equals("property")) {
newParseState = ESTRUCTURALFEATURE;
} else {
newParseState = ECLASS_ANNOTATION;
}
break;
case ESTRUCTURALFEATURE:
newParseState = ESTRUCTURALFEATURE_ANNOTATION;
break;
case EDATATYPE:
newParseState = EDATATYPE_ANNOTATION;
break;
case EPACKAGE_ANNOTATION:
case ECLASS_ANNOTATION:
case ESTRUCTURALFEATURE_ANNOTATION:
case NESTED_ANNOTATION: {
final EStructuralFeature annotationEStructuralFeature =
getEStructuralFeature(getPAnnotation(), localName);
if (annotationEStructuralFeature.getEType() instanceof EClass) {
newParseState = NESTED_ANNOTATION;
} else {
newParseState = ANNOTATION_ATTRIBUTE;
}
break;
}
default:
throw new ParseXMLAnnotationsException("Invalid parse state encountered.");
}
parseStates.push(new Integer(newParseState));
// Act upon the new parse state.
switch (getParseState()) {
case EPACKAGE: {
final String namespaceUri = attributes.getValue("namespace-uri");
final EPackage ePackage = EPackage.Registry.INSTANCE.getEPackage(namespaceUri);
if (ePackage == null) {
throw new SAXException("Could not find EPackage \"" + namespaceUri + "\".");
}
pAnnotatedEPackage = pAnnotatedModel.getPAnnotated(ePackage);
if (pAnnotatedEPackage == null) {
throw new SAXException("Could not find PAnnotatedEPackage \"" + namespaceUri + "\".");
}
break;
}
case ECLASS: {
final String eClassName = attributes.getValue("name");
final EClassifier eClassifier = pAnnotatedEPackage.getModelEPackage().getEClassifier(eClassName);
if (eClassifier == null) {
throw new SAXException("Could not find EClass \"" + eClassName + "\"");
}
if (!(eClassifier instanceof EClass)) {
throw new SAXException("EClassifier \"" + eClassName + "\" is not an EClass.");
}
pAnnotatedEClass = pAnnotatedModel.getPAnnotated((EClass) eClassifier);
break;
}
case EDATATYPE: {
final String eDataTypeName = attributes.getValue("name");
final EDataType et = (EDataType) pAnnotatedEPackage.getModelEPackage().getEClassifier(eDataTypeName);
if (et == null) {
throw new SAXException("Could not find EClass \"" + eDataTypeName + "\"");
}
pAnnotatedEDataType = pAnnotatedModel.getPAnnotated(et);
break;
}
case ESTRUCTURALFEATURE: {
final String eStructuralFeatureName = attributes.getValue("name");
final EClass eClass = pAnnotatedEClass.getModelEClass();
final EStructuralFeature eStructuralFeature = eClass.getEStructuralFeature(eStructuralFeatureName);
if (eStructuralFeature == null) {
throw new SAXException("Could not find EStructuralFeature \"" + eStructuralFeatureName +
"\" in EClass \"" + eClass.getName() + "\".");
} else if (localName.equals("eattribute") && !(eStructuralFeature instanceof EAttribute)) {
throw new SAXException("EStructuralFeature \"" + eStructuralFeatureName + "\" in EClass \"" +
eClass.getName() + "\" is not an EAttribute.");
} else if (localName.equals("ereference") && !(eStructuralFeature instanceof EReference)) {
throw new SAXException("EStructuralFeature \"" + eStructuralFeatureName + "\" in EClass \"" +
eClass.getName() + "\" is not an EReference.");
}
pAnnotatedEStructuralFeature = pAnnotatedModel.getPAnnotated(eStructuralFeature);
break;
}
case EPACKAGE_ANNOTATION:
applyAnnotation(pAnnotatedEPackage, localName, attributes);
break;
case ECLASS_ANNOTATION:
applyAnnotation(pAnnotatedEClass, localName, attributes);
break;
case ESTRUCTURALFEATURE_ANNOTATION:
applyAnnotation(pAnnotatedEStructuralFeature, localName, attributes);
break;
case EDATATYPE_ANNOTATION:
applyAnnotation(pAnnotatedEDataType, localName, attributes);
break;
case NESTED_ANNOTATION: {
// final String eStructuralFeatureName =
// convertElementNameToEStructuralFeatureName(localName);
// final EReference annotationEStructuralFeature = (EReference)
// getPAnnotation().eClass()
// .getEStructuralFeature(eStructuralFeatureName);
applyAnnotation(getPAnnotation(), localName, attributes);
break;
}
case ANNOTATION_ATTRIBUTE: {
final String eStructuralFeatureName = convertXmlNameToEStructuralFeatureName(localName);
pAnnotationEAttribute =
(EAttribute) getPAnnotation().eClass().getEStructuralFeature(eStructuralFeatureName);
break;
}
}
}
@Override
@SuppressWarnings("unchecked")
public void characters(char[] ch, int start, int length) throws SAXException {
final String value = new String(ch, start, length).trim();
if (value.length() == 0) {
return;
}
switch (getParseState()) {
case EPACKAGE_ANNOTATION:
case ECLASS_ANNOTATION:
case ESTRUCTURALFEATURE_ANNOTATION:
case NESTED_ANNOTATION: {
// If we get here, we are dealing with a PAnnotation that has only one EAttribute.
// I.e. there are no
// child elements. Example: <discriminator-value>MyObject</discriminator-value>
final PAnnotation pAnnotation = getPAnnotation();
assert (pAnnotation.eClass().getEStructuralFeatures().size() == 1);
final EAttribute eAttribute = (EAttribute) pAnnotation.eClass().getEStructuralFeatures().get(0);
final EDataType eAttributeType = eAttribute.getEAttributeType();
final Object valueObject =
eAttributeType.getEPackage().getEFactoryInstance().createFromString(eAttributeType, value);
pAnnotation.eSet(eAttribute, valueObject);
break;
}
case ANNOTATION_ATTRIBUTE: {
final EDataType eDataType = pAnnotationEAttribute.getEAttributeType();
final Object valueObject =
eDataType.getEPackage().getEFactoryInstance().createFromString(eDataType, value);
if (pAnnotationEAttribute.isMany()) {
((List) getPAnnotation().eGet(pAnnotationEAttribute)).add(valueObject);
} else {
getPAnnotation().eSet(pAnnotationEAttribute, valueObject);
}
break;
}
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
switch (getParseState()) {
case EPACKAGE_ANNOTATION:
case ECLASS_ANNOTATION:
case ESTRUCTURALFEATURE_ANNOTATION:
case NESTED_ANNOTATION:
pAnnotations.pop();
break;
case ANNOTATION_ATTRIBUTE:
pAnnotationEAttribute = null;
break;
}
parseStates.pop();
}
// --------------------------------------------------------------------
// Implementation of ErrorHandler interface.
// --------------------------------------------------------------------
@Override
public void error(SAXParseException e) throws SAXException {
throw e;
}
@Override
public void fatalError(SAXParseException e) throws SAXException {
throw e;
}
/**
* @return the extensionManager
*/
public ExtensionManager getExtensionManager() {
return extensionManager;
}
/**
* @param extensionManager
* the extensionManager to set
*/
public void setExtensionManager(ExtensionManager extensionManager) {
this.extensionManager = extensionManager;
}
/**
* @return the pAnnotatedModel
*/
public PAnnotatedModel getPAnnotatedModel() {
return pAnnotatedModel;
}
/**
* @param annotatedModel
* the pAnnotatedModel to set
*/
public void setPAnnotatedModel(PAnnotatedModel annotatedModel) {
pAnnotatedModel = annotatedModel;
}
/**
* @return the prefix
*/
public String getPrefix() {
return prefix;
}
/**
* @param prefix
* the prefix to set
*/
public void setPrefix(String prefix) {
this.prefix = prefix;
}
}