/*
* Copyright (c) 2006, 2009 Borland Software Corporation
*
* 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:
* Alexander Fedorov (Borland) - initial API and implementation
*/
package org.eclipse.gmf.internal.bridge.transform;
import java.io.IOException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.emf.codegen.ecore.genmodel.GenModel;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.ContentHandler;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.EcoreUtil.ExternalCrossReferencer;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.gmf.codegen.gmfgen.GenEditorGenerator;
import org.eclipse.gmf.graphdef.codegen.MapModeCodeGenStrategy;
import org.eclipse.gmf.internal.bridge.genmodel.BasicDiagramRunTimeModelHelper;
import org.eclipse.gmf.internal.bridge.genmodel.DiagramGenModelTransformer;
import org.eclipse.gmf.internal.bridge.genmodel.DiagramRunTimeModelHelper;
import org.eclipse.gmf.internal.bridge.genmodel.GenModelProducer;
import org.eclipse.gmf.internal.bridge.genmodel.InnerClassViewmapProducer;
import org.eclipse.gmf.internal.bridge.genmodel.ModeledViewmapProducer;
import org.eclipse.gmf.internal.bridge.genmodel.QVTDiagramGenModelTransformer;
import org.eclipse.gmf.internal.bridge.genmodel.ViewmapProducer;
import org.eclipse.gmf.internal.bridge.naming.gen.GenNamingMediatorImpl;
import org.eclipse.gmf.internal.bridge.ui.Plugin;
import org.eclipse.gmf.internal.codegen.util.GMFGenConfig;
import org.eclipse.gmf.internal.common.migrate.ModelLoadHelper;
import org.eclipse.gmf.internal.common.reconcile.Reconciler;
import org.eclipse.gmf.mappings.Mapping;
import org.eclipse.m2m.qvt.oml.BasicModelExtent;
import org.eclipse.m2m.qvt.oml.ExecutionContextImpl;
import org.eclipse.m2m.qvt.oml.ExecutionDiagnostic;
import org.eclipse.m2m.qvt.oml.TransformationExecutor;
//[artem] XXX Why it's in the bridge.ui???
public class TransformToGenModelOperation implements ITransformToGenModelOperation {
private URI myGMFGenModelURI;
private TransformOptions myOptions;
private Mapping myMapping;
private GenModelDetector myGMDetector;
private GenModel myGenModel;
private Diagnostic myMapmodelValidationResult = Diagnostic.CANCEL_INSTANCE;
private Diagnostic myGMFGenValidationResult = Diagnostic.CANCEL_INSTANCE;
private IStatus myStaleGenmodelStatus = Status.CANCEL_STATUS;
private final ResourceSet myResourceSet;
public TransformToGenModelOperation(ResourceSet rs) {
assert rs != null;
myResourceSet = rs;
this.myOptions = new TransformOptions();
}
public TransformOptions getOptions() {
return myOptions;
}
public URI getGenURI() {
return this.myGMFGenModelURI;
}
public void setGenURI(URI gmfGen) {
this.myGMFGenModelURI = gmfGen;
}
public GenModel getGenModel() {
return this.myGenModel;
}
public final ResourceSet getResourceSet() {
return myResourceSet;
}
Mapping getMapping() {
return this.myMapping;
}
private void setMapping(Mapping m, Diagnostic validationResult) {
this.myMapping = m;
this.myMapmodelValidationResult = validationResult;
myGMDetector = (m != null) ? new GenModelDetector(m) : null;
myGenModel = null;
}
private void setGMFGenValidationResult(Diagnostic validationResult) {
this.myGMFGenValidationResult = validationResult;
}
public GenModelDetector getGenModelDetector() {
return myGMDetector;
}
public Diagnostic getGMFGenValidationResult() {
return this.myGMFGenValidationResult;
}
public Diagnostic getMapmodelValidationResult() {
return this.myMapmodelValidationResult;
}
public IStatus getStaleGenmodelStatus() {
return this.myStaleGenmodelStatus;
}
public Mapping loadMappingModel(URI uri, IProgressMonitor pm) throws CoreException {
Mapping content = null;
Diagnostic validation = Diagnostic.CANCEL_INSTANCE;
IProgressMonitor monitor = null;
try {
if (uri == null) {
throw new IllegalArgumentException(Messages.TransformToGenModelOperation_e_null_map_uri);
}
monitor = (pm != null) ? new SubProgressMonitor(pm, 1, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK) : new NullProgressMonitor();
String cancelMessage = Messages.TransformToGenModelOperation_e_map_load_cancelled;
monitor.beginTask("", 100); //$NON-NLS-1$
subTask(monitor, 0, Messages.TransformToGenModelOperation_task_load, cancelMessage);
ModelLoadHelper loadHelper = new ModelLoadHelper(getResourceSet(), uri);
if (!loadHelper.isOK()) {
throw new CoreException(loadHelper.getStatus());
}
subTask(monitor, 20, Messages.TransformToGenModelOperation_task_validate, cancelMessage);
content = findMappingFromMappingModelRoot(loadHelper.getContentsRoot());
if (content == null) {
String msg = MessageFormat.format(Messages.TransformToGenModelOperation_e_wrong_root_element, loadHelper.getContentsRoot().getClass().getName());
throw new CoreException(Plugin.createError(msg, null));
}
validation = ValidationHelper.validate(content, true, monitor);
monitor.worked(60);
if (Diagnostic.CANCEL == validation.getSeverity()) {
throw new CoreException(Plugin.createCancel(cancelMessage));
}
return content;
} catch (CoreException e) {
throw e;
} catch (Exception e) {
IStatus error = Plugin.createError(Messages.TransformToGenModelOperation_e_load_mapping_model, e);
throw new CoreException(error);
} finally {
setMapping(content, validation);
if (monitor != null) {
monitor.done();
}
}
}
protected Mapping findMappingFromMappingModelRoot(EObject mappingModelRoot) {
return mappingModelRoot instanceof Mapping ? (Mapping)mappingModelRoot : null;
}
public GenModel findGenmodel() throws CoreException {
IStatus detect;
try {
checkMapping();
GenModelDetector gmd = getGenModelDetector();
detect = gmd.detect();
if (detect.isOK()) {
GenModel genModel = gmd.get(getResourceSet());
this.myGenModel = genModel;
return genModel;
}
} catch (Exception e) {
IStatus error = Plugin.createError(Messages.TransformToGenModelOperation_e_mapping_invalid, e);
throw new CoreException(error);
}
throw new CoreException(detect);
}
public GenModel loadGenModel(URI uri, IProgressMonitor pm) throws CoreException {
IProgressMonitor monitor = null;
try {
checkMapping();
monitor = (pm != null) ? new SubProgressMonitor(pm, 1, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK) : new NullProgressMonitor();
String cancelMessage = Messages.TransformToGenModelOperation_e_genmodel_load_cancelled;
monitor.beginTask("", 100); //$NON-NLS-1$
monitor.subTask(Messages.TransformToGenModelOperation_task_detect);
GenModelDetector gmd = getGenModelDetector();
IStatus status = Status.OK_STATUS;
if (uri == null) {
status = gmd.detect();
} else {
status = gmd.advise(uri);
}
if (!status.isOK()) {
throw new CoreException(status);
}
subTask(monitor, 30, Messages.TransformToGenModelOperation_task_load, cancelMessage);
GenModel genModel = gmd.get(getResourceSet());
if (genModel == null) {
if (uri == null) {
this.myStaleGenmodelStatus = Status.CANCEL_STATUS;
this.myGenModel = null;
return null;
}
IStatus notFound = Plugin.createError(Messages.GenModelDetector_e_not_found, null);
throw new CoreException(notFound);
}
subTask(monitor, 40, Messages.TransformToGenModelOperation_task_validate, cancelMessage);
StaleGenModelDetector staleDetector = new StaleGenModelDetector(genModel);
IStatus stale = staleDetector.detect();
this.myGenModel = genModel;
this.myStaleGenmodelStatus = stale;
return genModel;
} catch (CoreException e) {
throw e;
} catch (Exception e) {
IStatus error = Plugin.createError(Messages.TransformToGenModelOperation_e_genmodel_load, e);
throw new CoreException(error);
} finally {
if (monitor != null) {
monitor.done();
}
}
}
public IStatus executeTransformation(IProgressMonitor pm) {
IProgressMonitor monitor = null;
Diagnostic validation = Diagnostic.CANCEL_INSTANCE;
try {
if (getGenURI() == null) {
throw new IllegalStateException(Messages.TransformToGenModelOperation_e_null_gmfgen_uri);
}
checkMapping();
monitor = (pm != null) ? new SubProgressMonitor(pm, 1, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK) : new NullProgressMonitor();
monitor.beginTask("", 100); //$NON-NLS-1$
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
final VisualIdentifierDispenserProvider idDispenser = getVisualIdDispenser();
idDispenser.acquire();
GenModelProducer t = createGenModelProducer(idDispenser);
monitor.subTask(Messages.TransformToGenModelOperation_task_generate);
GenEditorGenerator genEditor = t.process(getMapping(), new SubProgressMonitor(monitor, 20));
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
monitor.subTask(Messages.TransformToGenModelOperation_task_reconcile);
if (Plugin.needsReconcile()) {
handlePreReconcileHooks(genEditor);
reconcile(genEditor);
handlePostReconcileHooks(genEditor);
}
if (hasExtensionTransformation(getMapping().eResource().getURI())) {
executeExtensionTransformation(getMapping().eResource().getURI(), genEditor);
}
GenNamingMediatorImpl namer = new GenNamingMediatorImpl();
namer.setMode(GenNamingMediatorImpl.Mode.COLLECT_NAMES);
namer.traverse(genEditor); // collect reconciled names
namer.setMode(GenNamingMediatorImpl.Mode.DISPENSE_NAMES);
namer.traverse(genEditor); // dispense names to new elements
monitor.worked(20);
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
monitor.subTask(Messages.TransformToGenModelOperation_task_save);
save(genEditor);
monitor.worked(20);
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
monitor.subTask(Messages.TransformToGenModelOperation_task_validate);
try {
validation = ValidationHelper.validate(genEditor, true, monitor);
} catch (RuntimeException re) {
validation = BasicDiagnostic.toDiagnostic(re);
}
if (Diagnostic.CANCEL != validation.getSeverity()) {
idDispenser.release();
}
return Status.OK_STATUS;
} catch (CoreException ex) {
return ex.getStatus();
} catch (Exception ex) {
String message = ex.getMessage();
if (message == null) {
message = Messages.TransformToGenModelOperation_e_generator_creation;
}
return Plugin.createError(message, ex);
} finally {
setGMFGenValidationResult(validation);
if (monitor != null) {
monitor.done();
}
}
}
protected boolean hasExtensionTransformation(URI uri) {
final URI transfURI = uri.trimFileExtension().appendFileExtension("qvto");
final TransformationExecutor executor = new TransformationExecutor(transfURI);
Diagnostic diag = executor.loadTransformation();
return diag.getCode() == Diagnostic.OK;
}
protected void executeExtensionTransformation(URI uri, GenEditorGenerator result) {
final URI transfURI = uri.trimFileExtension().appendFileExtension("qvto");
final TransformationExecutor executor = new TransformationExecutor(transfURI);
final ExecutionContextImpl context = new ExecutionContextImpl();
executor.execute(context, new BasicModelExtent(Arrays.asList(new GenEditorGenerator[] { result })));
}
protected void handlePreReconcileHooks(GenEditorGenerator result) {
if (getOptions().getPreReconcileTransform() != null) {
URI transfURI = URI.createURI(getOptions().getPreReconcileTransform().toExternalForm());
final TransformationExecutor executor = new TransformationExecutor(transfURI);
final ExecutionContextImpl context = new ExecutionContextImpl();
executor.execute(context, new BasicModelExtent(Arrays.asList(new GenEditorGenerator[] { result })));
}
}
protected void handlePostReconcileHooks(GenEditorGenerator result) {
if (getOptions().getPostReconcileTransform() != null) {
URI transfURI = URI.createURI(getOptions().getPostReconcileTransform().toExternalForm());
final TransformationExecutor executor = new TransformationExecutor(transfURI);
final ExecutionContextImpl context = new ExecutionContextImpl();
executor.execute(context, new BasicModelExtent(Arrays.asList(new GenEditorGenerator[] { result })));
}
}
private void checkMapping() {
if (getMapping() == null) {
throw new IllegalStateException(Messages.TransformToGenModelOperation_e_null_mapping);
}
}
static IStatus getFirst(Diagnostic d) {
if (d == null) {
return Status.OK_STATUS;
}
List<Diagnostic> children = d.getChildren();
if (children.isEmpty()) {
return BasicDiagnostic.toIStatus(d);
} else {
return BasicDiagnostic.toIStatus(children.get(0));
}
}
private DiagramRunTimeModelHelper detectRunTimeModel() {
return new BasicDiagramRunTimeModelHelper();
}
private ViewmapProducer detectTransformationOptions() {
boolean useModeledViewmaps = !getOptions().getUseInTransformationCodeGen();
if (useModeledViewmaps) {
return new ModeledViewmapProducer();
}
String runtimeToken = getOptions().getUseRuntimeFigures() ? "full" : "lite";
MapModeCodeGenStrategy mmStrategy = getOptions().getUseMapMode() ? MapModeCodeGenStrategy.DYNAMIC : MapModeCodeGenStrategy.STATIC;
URL dynamicFigureTemplates = getOptions().getFigureTemplatesPath();
return new InnerClassViewmapProducer(runtimeToken, mmStrategy, dynamicFigureTemplates == null ? null : new URL[] { dynamicFigureTemplates });
}
private VisualIdentifierDispenserProvider getVisualIdDispenser() {
return new VisualIdentifierDispenserProvider(getGenURI());
}
private GenModelProducer createGenModelProducer(VisualIdentifierDispenserProvider idDespenser) {
if (getOptions().getMainTransformation() != null) {
final ExecutionContextImpl context = new ExecutionContextImpl();
context.setConfigProperty("rcp", getOptions().getGenerateRCP());
context.setConfigProperty("useMapMode", getOptions().getUseMapMode());
context.setConfigProperty("useFullRunTime", getOptions().getUseRuntimeFigures());
context.setConfigProperty("useInTransformationCodeGen", getOptions().getUseInTransformationCodeGen());
final QVTDiagramGenModelTransformer transformer = new QVTDiagramGenModelTransformer(getResourceSet(), idDespenser.get());
transformer.setTransformationL(getOptions().getMainTransformation());
return new GenModelProducer() {
public GenEditorGenerator process(Mapping mapping, IProgressMonitor progress) throws CoreException {
progress.beginTask(null, 1);
try {
final BasicModelExtent output = new BasicModelExtent();
final ExecutionDiagnostic result = transformer.transform(mapping, getGenModel(), output, context);
if (Plugin.printTransformationConsole()) {
System.err.println(result.getMessage());
}
if (result.getSeverity() == Diagnostic.OK) {
List<EObject> outObjects = output.getContents();
return outObjects.get(0) instanceof GenEditorGenerator ? (GenEditorGenerator) outObjects.get(0) : null;
}
String errorMessage = result.getMessage();
if (errorMessage == null || errorMessage.isEmpty()) {
errorMessage = "Transformation has no out parameter of GenEditorGenerator type";
}
throw new CoreException(new Status(IStatus.ERROR, Plugin.getPluginID(), errorMessage));
} finally {
progress.done();
}
}
};
} else {
final DiagramRunTimeModelHelper drtModelHelper = detectRunTimeModel();
final ViewmapProducer viewmapProducer = detectTransformationOptions();
DiagramGenModelTransformer.Parameters opts = new DiagramGenModelTransformer.Parameters(drtModelHelper, viewmapProducer, idDespenser.get(), getOptions().getGenerateRCP());
final DiagramGenModelTransformer t = new DiagramGenModelTransformer(opts);
if (getGenModel() != null) {
t.setEMFGenModel(getGenModel());
}
return new GenModelProducer() {
public GenEditorGenerator process(Mapping mapping, IProgressMonitor progress) {
progress.beginTask(null, 1);
try {
t.transform(mapping);
return t.getResult();
} finally {
progress.done();
}
}
};
}
}
private void reconcile(GenEditorGenerator genBurdern) {
GenEditorGenerator old = null;
Resource resource = null;
try {
resource = getResourceSet().getResource(getGenURI(), false);
if (resource == null) {
resource = getResourceSet().createResource(getGenURI(), ContentHandler.UNSPECIFIED_CONTENT_TYPE);
resource.load(getResourceSet().getLoadOptions());
}
List<EObject> contents = resource.getContents();
if (!contents.isEmpty() && contents.get(0) instanceof GenEditorGenerator) {
old = (GenEditorGenerator) contents.get(0);
}
if (old != null) {
new Reconciler(new GMFGenConfig()).reconcileTree(genBurdern, old);
}
} catch (IOException e) {
// can't load resource, means no old file, IGNORE the exception
} catch (RuntimeException e) {
Plugin.log(e);
old = null;
} finally {
if (resource != null) {
if (resource.isLoaded()) {
// not sure I need to unload given I'll remove the resource from resource set anyway, but it doesn't hurt?
resource.unload();
}
// Need to remove created resource from resource set, not to affect further load attempts
// (e.g. the one in #save() method, with another content type)
// Another option would be use of correct content type here, but what
// if loaded/reconciled model has old content type?
getResourceSet().getResources().remove(resource);
}
}
}
private void save(GenEditorGenerator genBurdern) throws IOException {
HashMap<String, Object> saveOptions = new HashMap<String, Object>();
saveOptions.put(XMLResource.OPTION_ENCODING, "UTF-8"); //$NON-NLS-1$
Resource gmfgenRes = getResourceSet().getResource(getGenURI(), false);
try {
if (gmfgenRes == null) {
gmfgenRes = getResourceSet().createResource(getGenURI(), "org.eclipse.gmf.gen" /*GMFGen contentType, defined in oeg.codegen*/); //$NON-NLS-1$
gmfgenRes.load(getResourceSet().getLoadOptions());
}
updateExistingResource(gmfgenRes, genBurdern);
// one might want to ignore dangling href on save when there are more than one
// content object - there are chances we don't match them during reconcile and
// failed update all the references.
if (gmfgenRes.getContents().size() > 1 && Plugin.ignoreDanglingHrefOnSave()) {
saveOptions.put(XMLResource.OPTION_PROCESS_DANGLING_HREF, XMLResource.OPTION_PROCESS_DANGLING_HREF_RECORD);
}
gmfgenRes.save(saveOptions);
} catch (IOException ex) {
// load failed, no file exists
gmfgenRes.getContents().add(genBurdern);
gmfgenRes.save(saveOptions);
} catch (RuntimeException ex) {
Plugin.log(ex);
// save anyway, for later examination
gmfgenRes.getContents().add(genBurdern);
gmfgenRes.save(saveOptions);
}
}
private static void updateExistingResource(Resource gmfgenRes, GenEditorGenerator genBurden) {
boolean editorGenFound = false;
for (int i = 0; !editorGenFound && i < gmfgenRes.getContents().size(); i++) {
if (gmfgenRes.getContents().get(i) instanceof GenEditorGenerator) {
if (gmfgenRes.getContents().size() > 1) {
// chances there are other content eobjects that reference
// some parts of old GenEditorGenerator, hence need update
LinkedList<EObject> rest = new LinkedList<EObject>(gmfgenRes.getContents());
GenEditorGenerator oldEditorGenerator = (GenEditorGenerator) rest.remove(i);
updateExternalReferences(genBurden, oldEditorGenerator, rest);
}
gmfgenRes.getContents().set(i, genBurden); // replace with new one
editorGenFound = true;
}
}
if (!editorGenFound) {
gmfgenRes.getContents().add(genBurden);
}
}
private static void updateExternalReferences(GenEditorGenerator newEditorGenerator, final GenEditorGenerator oldEditorGenerator, List<EObject> allContentButOldGenerator) {
// find references from rest of the content to old generator
final Map<EObject, Collection<EStructuralFeature.Setting>> crossReferences = new ExternalCrossReferencer(allContentButOldGenerator) {
private static final long serialVersionUID = 4383601037841211175L;
@Override
protected boolean crossReference(EObject object, EReference reference, EObject crossReferencedEObject) {
return super.crossReference(object, reference, crossReferencedEObject) && EcoreUtil.isAncestor(oldEditorGenerator, crossReferencedEObject);
}
Map<EObject, Collection<EStructuralFeature.Setting>> find() {
return findExternalCrossReferences();
}
}.find();
// match new and old objects using reconciler without decisions
new Reconciler(new GMFGenConfig()) {
@Override
protected void handleNotMatchedCurrent(EObject current) {/* no-op */
};
@Override
protected EObject handleNotMatchedOld(EObject currentParent, EObject notMatchedOld) {
return null; /* no-op */
};
@Override
protected void reconcileVertex(EObject current, EObject old) {
if (!crossReferences.containsKey(old)) {
return;
}
// and replace old values with new
for (EStructuralFeature.Setting s : crossReferences.get(old)) {
EcoreUtil.replace(s, old, current);
}
}
}.reconcileTree(newEditorGenerator, oldEditorGenerator);
}
private static void subTask(IProgressMonitor monitor, int ticks, String name, String cancelMessage) throws CoreException {
if (monitor == null) {
return;
}
if (monitor.isCanceled()) {
IStatus cancel = Plugin.createCancel(cancelMessage);
throw new CoreException(cancel);
}
if (ticks > 0) {
monitor.worked(ticks);
}
if (name != null) {
monitor.subTask(name);
}
}
}