blob: 4504c6a1cf23626a588459e0c4b7bfd91258e162 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2017 Willink Transformations 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:
* E.D.Willink - initial API and implementation
*******************************************************************************/
package org.eclipse.ocl.pivot.internal.ecore.as2es;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import org.eclipse.emf.codegen.ecore.genmodel.GenModelPackage;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EGenericType;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.Resource.Diagnostic;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.xmi.XMIException;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.ocl.common.OCLConstants;
import org.eclipse.ocl.pivot.Annotation;
import org.eclipse.ocl.pivot.CollectionType;
import org.eclipse.ocl.pivot.Comment;
import org.eclipse.ocl.pivot.Constraint;
import org.eclipse.ocl.pivot.Detail;
import org.eclipse.ocl.pivot.Element;
import org.eclipse.ocl.pivot.LanguageExpression;
import org.eclipse.ocl.pivot.Model;
import org.eclipse.ocl.pivot.PivotPackage;
import org.eclipse.ocl.pivot.Type;
import org.eclipse.ocl.pivot.internal.delegate.DelegateInstaller;
import org.eclipse.ocl.pivot.internal.resource.StandaloneProjectMap;
import org.eclipse.ocl.pivot.internal.utilities.AbstractConversion;
import org.eclipse.ocl.pivot.internal.utilities.EnvironmentFactoryInternal;
import org.eclipse.ocl.pivot.internal.utilities.PivotConstantsInternal;
import org.eclipse.ocl.pivot.internal.utilities.PivotObjectImpl;
import org.eclipse.ocl.pivot.options.OCLinEcoreOptions;
import org.eclipse.ocl.pivot.resource.ProjectManager;
import org.eclipse.ocl.pivot.utilities.ClassUtil;
import org.eclipse.ocl.pivot.utilities.NameUtil;
import org.eclipse.ocl.pivot.utilities.PivotConstants;
import org.eclipse.ocl.pivot.utilities.PivotUtil;
import org.eclipse.ocl.pivot.utilities.StringUtil;
import org.eclipse.ocl.pivot.utilities.XMIUtil;
public class AS2Ecore extends AbstractConversion
{
public static final Logger logger = Logger.getLogger(AS2Ecore.class);
/**
* True to add comments to the invariant context and diagnostics parameters.
*/
public static final @NonNull String OPTION_ADD_INVARIANT_COMMENTS = "addInvariantComments";
/**
* True to apply result = () wrapper to invariant body.
*/
public static final @NonNull String OPTION_BOOLEAN_INVARIANTS = DelegateInstaller.OPTION_BOOLEAN_INVARIANTS;
/**
* True to apply a prefix to invariant names.
*/
public static final @NonNull String OPTION_INVARIANT_PREFIX = "invariantPrefix";
/**
* True to suppress the UML2Ecore duplicates EAnnotation. This is an experimental internal option used during
* the auto-generation of Pivot.ecore..
*/
public static final @NonNull String OPTION_SUPPRESS_DUPLICATES = "suppressDuplicates";
/**
* True to use XMIUtil.StructuralENamedElementIdCreator to assign xmi:ids.
*
* @since 1.3
*/
public static final @NonNull String OPTION_GENERATE_STRUCTURAL_XMI_IDS = "generateStructuralXmiIds";
public static void copyAnnotationComments(@NonNull EAnnotation eModelElement, @NonNull Constraint pivotConstraint) {
String key = DelegateInstaller.getAnnotationKey(pivotConstraint);
EAnnotation commentAnnotation = eModelElement.getEAnnotation(PivotConstantsInternal.DOCUMENTATION_ANNOTATION_SOURCE);
List<Comment> newComments = pivotConstraint.getOwnedComments();
int iMax = newComments.size();
if (iMax > 0) {
if (commentAnnotation == null) {
commentAnnotation = EcoreFactory.eINSTANCE.createEAnnotation();
commentAnnotation.setSource(PivotConstantsInternal.DOCUMENTATION_ANNOTATION_SOURCE);
eModelElement.getEAnnotations().add(commentAnnotation);
}
StringBuilder s = new StringBuilder();
for (int iComment = 0; iComment < iMax; iComment++) {
if (iComment > 0) {
s.append("\n");
}
s.append(newComments.get(iComment).getBody());
}
commentAnnotation.getDetails().put(key, s.toString());
}
else if (commentAnnotation != null) {
commentAnnotation.getDetails().removeKey(key);
}
}
/**
* @deprecated Use copyCommentsAndDocumentation
*/
@Deprecated
public static void copyComments(@NonNull EModelElement eModelElement, @NonNull Element pivotElement) {
copyCommentsAndDocumentation(eModelElement, pivotElement);
}
/**
* Create/add/remove a http://www.eclipse.org/emf/2002/GenModel::documentation detail to eModelElement
* to correspond to the splice of all pivotElement's Comment bodies and http://www.eclipse.org/emf/2002/GenModel
* Annotation documentation details.
*
* @since 1.3
*/
public static void copyCommentsAndDocumentation(@NonNull EModelElement eModelElement, @NonNull Element pivotElement) {
List<String> newComments = null;
for (Comment comment : pivotElement.getOwnedComments()) {
if (newComments == null) {
newComments = new ArrayList<>();
}
newComments.add(comment.getBody());
}
for (Element element : pivotElement.getOwnedAnnotations()) {
if (element instanceof Annotation) {
Annotation pivotAnnotation = (Annotation)element;
if (PivotConstantsInternal.DOCUMENTATION_ANNOTATION_SOURCE.equals(pivotAnnotation.getName())) {
Detail detail = NameUtil.getNameable(pivotAnnotation.getOwnedDetails(), PivotConstantsInternal.DOCUMENTATION_ANNOTATION_KEY);
if (detail != null) {
List<String> values = detail.getValues();
if (newComments == null) {
newComments = new ArrayList<>();
}
newComments.addAll(values);
}
}
}
}
if (newComments != null) {
List<EAnnotation> allEAnnotations = ClassUtil.nullFree(eModelElement.getEAnnotations());
EAnnotation eAnnotation = eModelElement.getEAnnotation(PivotConstantsInternal.DOCUMENTATION_ANNOTATION_SOURCE);
if (eAnnotation == null) {
eAnnotation = EcoreFactory.eINSTANCE.createEAnnotation();
eAnnotation.setSource(PivotConstantsInternal.DOCUMENTATION_ANNOTATION_SOURCE);
allEAnnotations.add(eAnnotation);
}
String value = StringUtil.splice(newComments, "");
eAnnotation.getDetails().put(PivotConstantsInternal.DOCUMENTATION_ANNOTATION_KEY, value);
}
else {
EAnnotation eAnnotation = eModelElement.getEAnnotation(PivotConstantsInternal.DOCUMENTATION_ANNOTATION_SOURCE);
if (eAnnotation != null) {
eAnnotation.getDetails().removeKey(PivotConstantsInternal.DOCUMENTATION_ANNOTATION_KEY);
}
}
}
public static @NonNull EOperation createConstraintEOperation(@NonNull Constraint pivotConstraint, @NonNull String operationName, @Nullable Map<@NonNull String, @Nullable Object> options) {
if (options == null) {
options = new HashMap<>();
}
boolean addInvariantComments = AS2Ecore.isAddInvariantComments(options);
EOperation eOperation = EcoreFactory.eINSTANCE.createEOperation();
eOperation.setName(operationName);
eOperation.setEType(EcorePackage.Literals.EBOOLEAN);
String originalName = PivotUtil.getName(pivotConstraint);
if (!operationName.equals(originalName)) {
NameUtil.setOriginalName(eOperation, originalName);
}
{
EParameter firstParameter = EcoreFactory.eINSTANCE.createEParameter();
firstParameter.setName("diagnostics");
firstParameter.setEType(EcorePackage.Literals.EDIAGNOSTIC_CHAIN);
eOperation.getEParameters().add(firstParameter);
if (addInvariantComments) {
EAnnotation eAnnotation = EcoreFactory.eINSTANCE.createEAnnotation();
eAnnotation.setSource(GenModelPackage.eNS_URI);
eAnnotation.getDetails().put("documentation", "The chain of diagnostics to which problems are to be appended.");
firstParameter.getEAnnotations().add(eAnnotation);
}
}
{
EParameter secondParameter = EcoreFactory.eINSTANCE.createEParameter();
secondParameter.setName("context");
EGenericType eGenericType = EcoreFactory.eINSTANCE.createEGenericType();
eGenericType.setEClassifier(EcorePackage.Literals.EMAP);
EGenericType firstTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
firstTypeArgument.setEClassifier(EcorePackage.Literals.EJAVA_OBJECT);
eGenericType.getETypeArguments().add(firstTypeArgument);
EGenericType secondTypeArgument = EcoreFactory.eINSTANCE.createEGenericType();
secondTypeArgument.setEClassifier(EcorePackage.Literals.EJAVA_OBJECT);
eGenericType.getETypeArguments().add(secondTypeArgument);
secondParameter.setEGenericType(eGenericType);
eOperation.getEParameters().add(secondParameter);
if (addInvariantComments) {
EAnnotation eAnnotation = EcoreFactory.eINSTANCE.createEAnnotation();
eAnnotation.setSource(GenModelPackage.eNS_URI);
eAnnotation.getDetails().put("documentation", "The cache of context-specific information.");
secondParameter.getEAnnotations().add(eAnnotation);
}
}
LanguageExpression specification = pivotConstraint.getOwnedSpecification();
if (specification != null) {
String body = specification.getBody();
if (body != null) {
EAnnotation eAnnotation = EcoreFactory.eINSTANCE.createEAnnotation();
eAnnotation.setSource(getExportDelegateURI(options));
if (DelegateInstaller.isBooleanInvariants(options)) {
body = "result = (" + body + ")";
}
eAnnotation.getDetails().put("body", body);
eOperation.getEAnnotations().add(eAnnotation);
}
}
copyCommentsAndDocumentation(eOperation, pivotConstraint);
return eOperation;
}
public static @NonNull XMLResource createResource(@NonNull EnvironmentFactoryInternal environmentFactory, @NonNull Resource asResource, @NonNull URI ecoreURI, @Nullable Map<@NonNull String, @Nullable Object> options) {
AS2Ecore converter = new AS2Ecore(environmentFactory, ecoreURI, options);
return converter.convertResource(asResource, ecoreURI);
}
public static @NonNull Boolean getBoolean(@Nullable Map<@NonNull String, @Nullable Object> options, @NonNull String key) {
if (options == null) {
return false;
}
Object value = options.get(key);
if (value instanceof Boolean) {
return (Boolean) value;
}
if (value != null) {
logger.error("Non-Boolean '" + key + "' for '" + value + "'");
}
return false;
}
public static @Nullable String getExportDelegateURI(@Nullable Map<@NonNull String, @Nullable Object> options) {
String exportDelegateURI = options != null ? (String)options.get(OCLConstants.OCL_DELEGATE_URI) : null;
return exportDelegateURI != null ? exportDelegateURI : OCLinEcoreOptions.EXPORT_DELEGATION_URI.getPreferredValue();
}
public static @Nullable String getInvariantPrefix(@Nullable Map<@NonNull String, @Nullable Object> options) {
Object invariantPrefix = options != null ? options.get(OPTION_INVARIANT_PREFIX) : null;
return invariantPrefix != null ? invariantPrefix.toString() : null;
}
public static @Nullable String getString(@Nullable Map<@NonNull String, @Nullable Object> options, @NonNull String key) {
if (options == null) {
return null;
}
Object value = options.get(key);
if (value instanceof String) {
return (String) value;
}
if (value != null) {
logger.error("Non-String '" + key + "' for '" + value + "'");
}
return null;
}
public static boolean isAddInvariantComments(@NonNull Map<@NonNull String, @Nullable Object> options) {
return Boolean.valueOf(String.valueOf(options.get(OPTION_ADD_INVARIANT_COMMENTS)));
}
public static boolean isBooleanInvariants(@NonNull Map<@NonNull String, @Nullable Object> options) {
return Boolean.valueOf(String.valueOf(options.get(OPTION_BOOLEAN_INVARIANTS)));
}
/**
* Mapping of pivot elements to the resulting E elements.
*/
private final @NonNull Map<@NonNull Element, @NonNull EModelElement> createMap = new HashMap<>();
/**
* Mapping of all E elements created during pass 1 that require further work
* with respect to the corresponding CS element in pass 2.
*/
private final @NonNull Set<@NonNull Element> deferMap = new HashSet<>();
private @Nullable List<Resource.@NonNull Diagnostic> errors = null;
protected final @NonNull Map<@NonNull String, @Nullable Object> options;
protected final @NonNull DelegateInstaller delegateInstaller;
protected final @NonNull AS2EcoreDeclarationVisitor pass1;
protected final @NonNull AS2EcoreReferenceVisitor pass2;
protected final @NonNull URI ecoreURI;
protected final @Nullable String primitiveTypesUriPrefix;
public AS2Ecore(@NonNull EnvironmentFactoryInternal environmentFactory, @NonNull URI ecoreURI, @Nullable Map<@NonNull String, @Nullable Object> options) {
super(environmentFactory);
this.options = options != null ? options : new HashMap<>();
this.delegateInstaller = new DelegateInstaller(environmentFactory, options);
this.pass1 = new AS2EcoreDeclarationVisitor(this);
this.pass2 = new AS2EcoreReferenceVisitor(this);
this.ecoreURI = ecoreURI;
this.primitiveTypesUriPrefix = getString(options, PivotConstants.PRIMITIVE_TYPES_URI_PREFIX);
}
/** @deprecated not used */
@Deprecated
protected @Nullable Object convert(@NonNull Element pivotObject) {
Object eObject = pass1.safeVisit(pivotObject);
for (Element eKey : deferMap) {
pass2.safeVisit(eKey);
}
return eObject;
}
public @NonNull XMLResource convertResource(@NonNull Resource asResource, @NonNull URI ecoreURI) {
ResourceSet resourceSet = environmentFactory.getResourceSet();
setGenerationInProgress(asResource, true);
try {
XMLResource ecoreResource = (XMLResource) resourceSet.createResource(ecoreURI);
List<EObject> contents = ecoreResource.getContents();
// contents.clear(); // FIXME workaround for BUG 465326
for (EObject eContent : asResource.getContents()) {
if (eContent instanceof Model) {
Object results = pass1.safeVisit((Model)eContent);
if (results instanceof List<?>) {
@SuppressWarnings("unchecked")
List<EObject> results2 = (List<EObject>)results;
contents.addAll(results2);
}
}
}
for (@NonNull Element eKey : deferMap) {
pass2.safeVisit(eKey);
}
for (@NonNull Element pivotElement : createMap.keySet()) {
EObject eObject = createMap.get(pivotElement);
PivotObjectImpl pivotObjectImpl = (PivotObjectImpl) pivotElement;
if (pivotObjectImpl.getESObject() == null) { // Bug 510729 avoid trashing OCLstdlib
pivotObjectImpl.setESObject(eObject);
}
}
if (Boolean.valueOf(String.valueOf(options.get(OPTION_GENERATE_STRUCTURAL_XMI_IDS)))) {
XMIUtil.assignIds(ecoreResource, new XMIUtil.StructuralENamedElementIdCreator(), null);
}
return ecoreResource;
}
finally {
setGenerationInProgress(asResource, false);
}
}
public void defer(@NonNull Element pivotElement) {
deferMap.add(pivotElement);
}
protected void error(@NonNull String message) {
List<@NonNull Diagnostic> errors2 = errors;
if (errors2 == null) {
errors = errors2 = new ArrayList<>();
}
errors2.add(new XMIException(message));
}
public <T extends EObject> @Nullable T getCreated(@NonNull Class<T> requiredClass, @NonNull Element pivotElement) {
EModelElement eModelElement = createMap.get(pivotElement);
// System.out.println("Get " + PivotUtil.debugSimpleName(pivotElement) + " " + PivotUtil.debugSimpleName(eModelElement));
if (eModelElement == null) {
Element primaryElement = metamodelManager.getPrimaryElement(pivotElement);
if (pivotElement != primaryElement) {
eModelElement = createMap.get(primaryElement);
}
}
if (eModelElement == null) {
return null;
}
if (!requiredClass.isAssignableFrom(eModelElement.getClass())) {
logger.error("Ecore " + eModelElement.getClass().getName() + "' element is not a '" + requiredClass.getName() + "'"); //$NON-NLS-1$
return null;
}
@SuppressWarnings("unchecked")
T castElement = (T) eModelElement;
return castElement;
}
public @NonNull DelegateInstaller getDelegateInstaller() {
return delegateInstaller;
}
public final @NonNull URI getEcoreURI() {
return ecoreURI;
}
public @NonNull Map<@NonNull String, @Nullable Object> getOptions() {
return options;
}
public String getPrimitiveTypesUriPrefix() {
return primitiveTypesUriPrefix;
}
/**
* Return the non-Null CollectionType if asType can use Ecore multiplicities to express the (outer) collection.
*/
public @Nullable CollectionType isEcoreCollection(@Nullable Type asType) {
if (!(asType instanceof CollectionType)) {
return null;
}
if (((CollectionType)asType).getUnspecializedElement() == standardLibrary.getCollectionType()) {
return null; // Collection(T) cannot be distinguished from concrete Ecore collections
}
return (CollectionType)asType;
}
/**
* Return tre if asPackage is a Pivot Metamodel.
*/
public boolean isPivot(org.eclipse.ocl.pivot.@NonNull Package asPackage) {
List<org.eclipse.ocl.pivot.Class> asTypes = asPackage.getOwnedClasses();
if (NameUtil.getNameable(asTypes, PivotPackage.Literals.ENUMERATION_LITERAL.getName()) == null) {
return false;
}
if (NameUtil.getNameable(asTypes, PivotPackage.Literals.EXPRESSION_IN_OCL.getName()) == null) {
return false;
}
if (NameUtil.getNameable(asTypes, PivotPackage.Literals.OPERATION_CALL_EXP.getName()) == null) {
return false;
}
if (NameUtil.getNameable(asTypes, PivotPackage.Literals.TEMPLATE_PARAMETER_SUBSTITUTION.getName()) == null) {
return false;
}
return true;
}
public boolean isSuppressDuplicates() {
return Boolean.valueOf(String.valueOf(options.get(OPTION_SUPPRESS_DUPLICATES)));
}
public void putCreated(@NonNull Element pivotElement, @NonNull EModelElement eModelElement) {
Element primaryElement = metamodelManager.getPrimaryElement(pivotElement);
// System.out.println("Put1 " + PivotUtil.debugSimpleName(pivotElement) + " " + PivotUtil.debugSimpleName(eModelElement));
EModelElement oldPivot = createMap.put(pivotElement, eModelElement);
assert oldPivot == null;
if ((pivotElement != primaryElement) && !createMap.containsKey(primaryElement)) {
// System.out.println("Put2 " + PivotUtil.debugSimpleName(pivotElement) + " " + PivotUtil.debugSimpleName(eModelElement));
createMap.put(primaryElement, eModelElement);
}
}
protected void setGenerationInProgress(@NonNull Resource asResource, boolean isLoading) {
for (EObject eRoot : asResource.getContents()) {
if (eRoot instanceof Model) {
for (org.eclipse.ocl.pivot.Package asPackage : ((Model)eRoot).getOwnedPackages()) {
if (asPackage != null) {
setGenerationInProgress(asPackage, isLoading);
}
}
}
}
}
protected void setGenerationInProgress(org.eclipse.ocl.pivot.@NonNull Package asPackage, boolean isGenerating) {
String nsUri = asPackage.getURI();
if (nsUri != null) {
ProjectManager projectManager = environmentFactory.getProjectManager();
@NonNull URI nsURI = URI.createURI(nsUri);
StandaloneProjectMap.IPackageDescriptor packageDescriptor = projectManager.getPackageDescriptor(nsURI);
if (packageDescriptor != null) {
StandaloneProjectMap.IResourceDescriptor resourceDescriptor = packageDescriptor.getResourceDescriptor();
ResourceSet resourceSet = environmentFactory.getResourceSet();
StandaloneProjectMap.IResourceLoadStatus resourceLoadStatus = resourceDescriptor.getResourceLoadStatus(resourceSet);
resourceLoadStatus.setGenerationInProgress(isGenerating);
}
}
for (org.eclipse.ocl.pivot.Package asNestedPackage : asPackage.getOwnedPackages()) {
if (asNestedPackage != null) {
setGenerationInProgress(asNestedPackage, isGenerating);
}
}
}
}