blob: 04bd1ccca29c86fd78c957342f9e46b88bfc4399 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2011 See4sys, itemis and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
*
* Contributors:
* See4sys - Initial API and implementation
* itemis - [343844] Enable multiple Xtend MetaModels to be configured on BasicM2xAction, M2xConfigurationWizard, and Xtend/Xpand/CheckJob
* itemis - [357813] Risk of NullPointerException when transforming models using M2MConfigurationWizard
* itemis - [358082] Precedence of Xtend MetaModels gets lost in Xtend/Xpand runtime enhancements implemented in Sphinx
* itemis - [358131] Make Xtend/Xpand/CheckJobs more robust against template file encoding mismatches
*
* </copyright>
*/
package org.eclipse.sphinx.xtendxpand.jobs;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.mwe.core.issues.Issues;
import org.eclipse.emf.mwe.core.issues.IssuesImpl;
import org.eclipse.emf.mwe.core.issues.MWEDiagnostic;
import org.eclipse.emf.mwe.core.monitor.ProgressMonitorAdapter;
import org.eclipse.emf.mwe.core.resources.ResourceLoaderFactory;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.sphinx.emf.model.IModelDescriptor;
import org.eclipse.sphinx.emf.model.ModelDescriptorRegistry;
import org.eclipse.sphinx.emf.mwe.resources.IWorkspaceResourceLoader;
import org.eclipse.sphinx.emf.util.EcorePlatformUtil;
import org.eclipse.sphinx.platform.IExtendedPlatformConstants;
import org.eclipse.sphinx.platform.util.StatusUtil;
import org.eclipse.sphinx.xtendxpand.CheckEvaluationRequest;
import org.eclipse.sphinx.xtendxpand.XtendEvaluationRequest;
import org.eclipse.sphinx.xtendxpand.internal.Activator;
import org.eclipse.xtend.check.CheckFacade;
import org.eclipse.xtend.expression.ExecutionContextImpl;
import org.eclipse.xtend.expression.ResourceManager;
import org.eclipse.xtend.expression.ResourceManagerDefaultImpl;
import org.eclipse.xtend.expression.TypeSystem;
import org.eclipse.xtend.expression.TypeSystemImpl;
import org.eclipse.xtend.expression.Variable;
import org.eclipse.xtend.typesystem.MetaModel;
public class CheckJob extends Job {
protected static final Log log = LogFactory.getLog(CheckJob.class);
/**
* The {@link MetaModel metamodel}s behind the model(s) to be checked.
*
* @see MetaModel
*/
protected List<MetaModel> metaModels = null;
/**
* The {@link TypeSystem} including the {@link MetaModel metamodel}s behind the model(s) to be transformed.
*/
protected TypeSystem typeSystem = null;
/**
* A collection of Check evaluation request.
*
* @see {@link CheckEvaluationRequest} class.
*/
protected Collection<CheckEvaluationRequest> checkEvaluationRequests;
/**
* The resource loader to be used when loading check files.
*/
private IWorkspaceResourceLoader workspaceResourceLoader;
/**
* Creates a {@link CheckJob} that validates a model based on given <code>metaModel</code> as specified by provided
* <code>checkEvaluationRequest</code>.
*
* @param name
* The name of the job.
* @param metaModel
* The {@link MetaModel metamodel} to be used.
* @param checkEvaluationRequest
* The {@link CheckEvaluationRequest Check evaluation request} to be processed.
* @see MetaModel
* @see CheckEvaluationRequest
*/
public CheckJob(String name, MetaModel metaModel, CheckEvaluationRequest checkEvaluationRequest) {
this(name, Collections.singletonList(metaModel), Collections.singleton(checkEvaluationRequest));
}
/**
* Creates a {@link CheckJob} that validates one or several models based on given <code>metaModel</code> as
* specified by provided <code>checkEvaluationRequests</code>.
*
* @param name
* The name of the job.
* @param metaModel
* The {@link MetaModel metamodel} to be used.
* @param checkEvaluationRequests
* The {@link CheckEvaluationRequest Check evaluation request}s to be processed.
* @see MetaModel
* @see CheckEvaluationRequest
*/
public CheckJob(String name, MetaModel metaModel, Collection<CheckEvaluationRequest> checkEvaluationRequests) {
this(name, Collections.singletonList(metaModel), checkEvaluationRequests);
}
/**
* Creates a {@link CheckJob} that validates a model based on given <code>metaModels</code> as specified by provided
* <code>checkEvaluationRequest</code>.
*
* @param name
* The name of the job.
* @param metaModels
* The {@link MetaModel metamodel}s to be used.
* @param checkEvaluationRequest
* The {@link CheckEvaluationRequest Check evaluation request} to be processed.
* @see MetaModel
* @see CheckEvaluationRequest
*/
public CheckJob(String name, List<MetaModel> metaModels, CheckEvaluationRequest checkEvaluationRequest) {
this(name, metaModels, Collections.singleton(checkEvaluationRequest));
}
/**
* Creates a {@link CheckJob} that validates one or several models based on given <code>metaModels</code> as
* specified by provided <code>checkEvaluationRequests</code>.
*
* @param name
* The name of the job.
* @param metaModels
* The {@link MetaModel metamodel}s to be used.
* @param checkEvaluationRequests
* The {@link CheckEvaluationRequest Check evaluation request}s to be processed.
* @see MetaModel
* @see CheckEvaluationRequest
*/
public CheckJob(String name, List<MetaModel> metaModels, Collection<CheckEvaluationRequest> checkEvaluationRequests) {
super(name);
Assert.isNotNull(metaModels);
Assert.isNotNull(checkEvaluationRequests);
this.metaModels = metaModels;
this.checkEvaluationRequests = checkEvaluationRequests;
}
/**
* Creates an {@link CheckJob} that validates a model based on given <code>typeSystem</code> as specified by
* provided <code>checkEvaluationRequest</code>.
*
* @param name
* The name of the job.
* @param typeSystem
* The {@link TypeSystem type system} that includes the {@link MetaModel metamodel}s to be used.
* @param checkEvaluationRequest
* The {@link CheckEvaluationRequest Check evaluation request} to be processed.
* @see TypeSystem
* @see XtendEvaluationRequest
*/
public CheckJob(String name, TypeSystem typeSystem, CheckEvaluationRequest checkEvaluationRequest) {
this(name, typeSystem, Collections.singleton(checkEvaluationRequest));
}
/**
* Creates an {@link CheckJob} that validates one or several models based on given <code>typeSystem</code> as
* specified by provided <code>checkEvaluationRequests</code>.
*
* @param name
* The name of the job.
* @param typeSystem
* The {@link TypeSystem type system} that includes the {@link MetaModel metamodel}s to be used.
* @param checkEvaluationRequests
* The {@link CheckEvaluationRequest Check evaluation request}s to be processed.
* @see TypeSystem
* @see XtendEvaluationRequest
*/
public CheckJob(String name, TypeSystem typeSystem, Collection<CheckEvaluationRequest> checkEvaluationRequests) {
super(name);
Assert.isNotNull(typeSystem);
Assert.isNotNull(checkEvaluationRequests);
this.typeSystem = typeSystem;
this.checkEvaluationRequests = checkEvaluationRequests;
}
protected Map<TransactionalEditingDomain, Collection<CheckEvaluationRequest>> getCheckEvaluationRequests() {
Map<TransactionalEditingDomain, Collection<CheckEvaluationRequest>> requests = new HashMap<TransactionalEditingDomain, Collection<CheckEvaluationRequest>>();
for (CheckEvaluationRequest request : checkEvaluationRequests) {
TransactionalEditingDomain editingDomain = TransactionUtil.getEditingDomain(request.getModelRootObject());
Collection<CheckEvaluationRequest> requestsInEditingDomain = requests.get(editingDomain);
if (requestsInEditingDomain == null) {
requestsInEditingDomain = new HashSet<CheckEvaluationRequest>();
requests.put(editingDomain, requestsInEditingDomain);
}
requestsInEditingDomain.add(request);
}
return requests;
}
/**
* Sets the {@link IWorkspaceResourceLoader resource loader} for resolving resources referenced by Check
* constraints.
*
* @param resourceLoader
* The resource loader to be used.
*/
public void setWorkspaceResourceLoader(IWorkspaceResourceLoader resourceLoader) {
workspaceResourceLoader = resourceLoader;
}
@Override
public IStatus run(IProgressMonitor monitor) {
try {
// Log start of validation
log.info("Check model started..."); //$NON-NLS-1$
// Install resource loader
installResourceLoader();
// Create execution context
final ResourceManager resourceManager = new ResourceManagerDefaultImpl();
Map<String, Variable> variables = new HashMap<String, Variable>();
Map<String, Variable> globalVarsMap = new HashMap<String, Variable>();
final ExecutionContextImpl execCtx = new ExecutionContextImpl(resourceManager, null,
typeSystem instanceof TypeSystemImpl ? (TypeSystemImpl) typeSystem : new TypeSystemImpl(), variables, globalVarsMap,
new ProgressMonitorAdapter(monitor), null, null, null, null, null, null, null);
if (metaModels != null) {
for (MetaModel metaModel : metaModels) {
execCtx.registerMetaModel(metaModel);
}
}
// Execute validation
long startTime = System.currentTimeMillis();
final Issues diagIssues = new IssuesImpl();
final Map<TransactionalEditingDomain, Collection<CheckEvaluationRequest>> requests = getCheckEvaluationRequests();
for (final TransactionalEditingDomain editingDomain : requests.keySet()) {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (CheckEvaluationRequest request : requests.get(editingDomain)) {
// Update resource loader context
updateResourceLoaderContext(request.getModelRootObject());
// Evaluate check files
for (IFile file : request.getCheckFiles()) {
preCheck(request, file);
// Update resource manager with file encoding information for next Check file to be
// evaluated
try {
resourceManager.setFileEncoding(file.getCharset());
} catch (CoreException ex) {
// Ignore exception
}
// Evaluate current check file
String path = file.getProjectRelativePath().removeFileExtension().toString();
CheckFacade.checkAll(path, request.getModelObjects(), execCtx, diagIssues, false);
// Handle errors or warnings if any
postCheck(request, file, diagIssues);
}
}
}
};
if (editingDomain != null) {
editingDomain.runExclusive(runnable);
} else {
runnable.run();
}
}
long duration = System.currentTimeMillis() - startTime;
return endCheck(diagIssues, duration);
} catch (OperationCanceledException exception) {
return Status.CANCEL_STATUS;
} catch (Exception ex) {
return StatusUtil.createErrorStatus(Activator.getPlugin(), ex);
} finally {
// Always uninstall resource loader again
uninstallResourceLoader();
}
}
protected void preCheck(CheckEvaluationRequest request, IFile checkFile) {
log.info("Check model with '" + checkFile.getFullPath().makeRelative() + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //);
}
protected void postCheck(CheckEvaluationRequest request, IFile checkFile, Issues diagIssues) {
List<MWEDiagnostic> diagnostics = new ArrayList<MWEDiagnostic>();
diagnostics.addAll(Arrays.asList(diagIssues.getErrors()));
diagnostics.addAll(Arrays.asList(diagIssues.getWarnings()));
for (Iterator<MWEDiagnostic> iterator = diagnostics.iterator(); iterator.hasNext();) {
MWEDiagnostic diagnostic = iterator.next();
String message = diagnostic.getMessage() + (iterator.hasNext() ? "" : "\n"); //$NON-NLS-1$ //$NON-NLS-2$
switch (diagnostic.getSeverity()) {
case IStatus.ERROR:
log.error(message);
break;
case IStatus.WARNING:
log.warn(message);
break;
default:
break;
}
diagIssues.add(diagnostic);
}
}
protected IStatus endCheck(final Issues diagIssues, long duration) {
// Log errors and warnings encountered during validation and return appropriate status
if (diagIssues.hasErrors()) {
log.error("Check model failed in " + duration + "ms!\n"); //$NON-NLS-1$ //$NON-NLS-2$
return StatusUtil.createErrorStatus(Activator.getPlugin(), "Check model failed with errors"); //$NON-NLS-1$
}
if (diagIssues.hasWarnings()) {
return StatusUtil.createWarningStatus(Activator.getPlugin(), "Check model failed with warnings"); //$NON-NLS-1$
}
log.info("Check model completed in " + duration + "ms!\n"); //$NON-NLS-1$ //$NON-NLS-2$
return Status.OK_STATUS;
}
/**
* Installs a {@link IWorkspaceResourceLoader resource loader}.
*/
protected void installResourceLoader() {
if (workspaceResourceLoader == null) {
if (ResourceLoaderFactory.getCurrentThreadResourceLoader() instanceof IWorkspaceResourceLoader) {
workspaceResourceLoader = (IWorkspaceResourceLoader) ResourceLoaderFactory.getCurrentThreadResourceLoader();
}
} else {
ResourceLoaderFactory.setCurrentThreadResourceLoader(workspaceResourceLoader);
}
}
/**
* Updates context of current {@link IWorkspaceResourceLoader resource loader} according to given
* <code>contextObject</code>.
*/
protected void updateResourceLoaderContext(Object contextObject) {
if (workspaceResourceLoader != null) {
IFile contextFile = EcorePlatformUtil.getFile(contextObject);
IModelDescriptor contextModel = ModelDescriptorRegistry.INSTANCE.getModel(contextFile);
if (contextModel != null) {
workspaceResourceLoader.setContextModel(contextModel);
}
}
}
/**
* Uninstalls current {@link IWorkspaceResourceLoader resource loader}.
*/
protected void uninstallResourceLoader() {
ResourceLoaderFactory.setCurrentThreadResourceLoader(null);
}
@Override
public boolean belongsTo(Object family) {
return IExtendedPlatformConstants.FAMILY_LONG_RUNNING.equals(family);
}
}