/*=============================================================================#
# Copyright (c) 2015-2016 Stephan Wahlbrink (WalWare.de) 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:
# Stephan Wahlbrink - initial API and implementation
#=============================================================================*/
package de.walware.docmlet.base.ui.processing;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.statushandlers.StatusManager;
import de.walware.jcommons.collections.IdentityCollection;
import de.walware.ecommons.ICommonStatusConstants;
import de.walware.ecommons.debug.core.model.AbstractProcess;
import de.walware.ecommons.runtime.core.util.EnrichProgressMonitor;
import de.walware.docmlet.base.internal.ui.processing.Messages;
import de.walware.docmlet.base.ui.DocBaseUI;
import de.walware.docmlet.base.ui.processing.DocProcessingToolConfig.StepConfig;
public class DocProcessingToolProcess extends AbstractProcess {
private static final int CONTEXT_TICKS= 10;
private static final int STEP_TICKS= 10;
private static String createName(final ILaunchConfiguration config, final IFile file) {
final String[] names= new String[3];
try {
names[0]= config.getType().getName();
}
catch (final CoreException e) {}
if (names[0] == null) {
names[0]= "Document Processing"; //$NON-NLS-1$
}
names[1]= config.getName();
names[2]= file.getName();
return NLS.bind(Messages.ProcessingProcess_label, names);
}
private final DocProcessingToolConfig config;
private final Image image;
private EnrichProgressMonitor enrichMonitor;
private SubMonitor monitor;
private Thread workerThread;
private final MultiStatus status;
private List<DocProcessingToolOperationContext> contexts;
private final DocProcessingToolOperationIterator nextContextIterator;
private String currentContextId;
private DocProcessingToolOperationContext currentContext;
private final DocProcessingToolOperationIterator operationIterator;
private StepConfig currentStepConfig;
public DocProcessingToolProcess(final ILaunch launch, final DocProcessingToolConfig config) {
super(launch, createName(launch.getLaunchConfiguration(), config.getSourceFile()));
this.config= config;
this.operationIterator= new DocProcessingToolOperationIterator(config.getSteps());
this.nextContextIterator= new DocProcessingToolOperationIterator(config.getSteps());
this.status= new MultiStatus(DocBaseUI.PLUGIN_ID, ICommonStatusConstants.LAUNCHING,
getLabel(), null);
this.image= fetchImage();
}
protected Image fetchImage() {
try {
final ILaunchConfiguration configuration= getLaunch().getLaunchConfiguration();
if (configuration != null) {
final String contentTypeId= configuration.getAttribute(
DocProcessingUI.CONTENT_TYPE_ID_ATTR_NAME, (String) null);
DocProcessingManager manager= null;
if (contentTypeId != null) {
DocProcessingUI.getDocProcessingManager(contentTypeId);
}
else {
final IContentType contentType= this.config.getSourceFile()
.getContentDescription().getContentType();
if (contentType != null) {
manager= DocProcessingUI.getDocProcessingManager(contentType, true);
}
}
if (manager != null) {
return manager.getActionImage((IdentityCollection<String>)
configuration.getAttribute(
DocProcessingUI.RUN_STEPS_ATTR_NAME, Collections.EMPTY_SET ));
}
}
}
catch (final Exception e) {}
return null;
}
public Image getImage() {
return this.image;
}
public DocProcessingToolConfig getConfig() {
return this.config;
}
public final MultiStatus getStatus() {
return this.status;
}
public void check(final IStatus status) throws CoreException {
switch (status.getSeverity()) {
case IStatus.OK:
return;
case IStatus.INFO:
case IStatus.WARNING:
log(status);
return;
default:
throw new CoreException(status);
}
}
public void log(final IStatus status) {
synchronized (this.status) {
if (status.getSeverity() == IStatus.CANCEL
&& (this.status.getSeverity() == IStatus.ERROR
|| (this.status.getSeverity() == IStatus.CANCEL && status == Status.CANCEL_STATUS) )) {
return;
}
this.status.add(status);
}
}
protected boolean shouldContinue() {
final int severity;
synchronized (this.status) {
severity= this.status.getSeverity();
if (severity >= IStatus.ERROR) {
return false;
}
final IProgressMonitor monitor= this.monitor;
if (monitor != null && monitor.isCanceled()) {
this.status.add(Status.CANCEL_STATUS);
return false;
}
return true;
}
}
public ISchedulingRule beginSchedulingRule(final ISchedulingRule rule,
final IProgressMonitor monitor) throws OperationCanceledException {
if (rule != null) {
Job.getJobManager().beginRule(rule, monitor);
}
return rule;
}
public void endSchedulingRule(final ISchedulingRule rule) {
if (rule != null) {
Job.getJobManager().endRule(rule);
}
}
public DocProcessingToolOperationContext getCurrentOperationContext() {
return this.currentContext;
}
public String getCurrentStepLabel() {
final StepConfig stepConfig= this.operationIterator.getStepConfig();
return (stepConfig != null) ? stepConfig.getLabel() : "";
}
@Override
public boolean canTerminate() {
return (this.monitor != null);
}
@Override
public boolean isTerminated() {
return (this.monitor == null);
}
@Override
public void terminate() throws DebugException {
{ final IProgressMonitor monitor= this.monitor;
if (monitor != null) {
monitor.setCanceled(true);
}
}
{ final DocProcessingToolOperationContext context= this.currentContext;
if (context != null) {
context.cancel();
}
}
{ final Thread thread= this.workerThread;
if (thread != null) {
thread.interrupt();
}
}
}
public IStatus run(final IProgressMonitor monitor) {
this.enrichMonitor= (monitor != null) ? new EnrichProgressMonitor(monitor) : null;
this.monitor= SubMonitor.convert(this.enrichMonitor, getLabel(), calculateTicks());
this.workerThread= Thread.currentThread();
created();
try {
runProcessing(this.monitor);
}
catch (final Throwable e) {
doSetExitValue(-1);
log(new Status(IStatus.ERROR, DocBaseUI.PLUGIN_ID, 0,
Messages.ProcessingProcess_error_UnexpectedError_message,
e ));
}
finally {
runFinished();
}
return getStatus();
}
protected void stepChanged(final StepConfig finishedStepConfig, final StepConfig nextStepConfig) {
if (finishedStepConfig != null) {
final SubMonitor m= this.monitor;
if (m != null) {
m.worked(STEP_TICKS);
}
}
this.currentStepConfig= nextStepConfig;
{ final EnrichProgressMonitor m= this.enrichMonitor;
if (m != null) {
m.setPrefix((nextStepConfig != null) ?
'[' + nextStepConfig.getLabel() + "] " : //$NON-NLS-1$
null );
}
}
}
protected void runFinished() {
if (this.status.getSeverity() > IStatus.WARNING) {
int exitValue= doGetExitValue();
if (exitValue == 0) {
doSetExitValue(exitValue= 1);
}
log(new Status(IStatus.INFO, DocBaseUI.PLUGIN_ID,
"Exit code= " + exitValue ));
}
if (this.status.getSeverity() > IStatus.INFO) {
StatusManager.getManager().handle(this.status, StatusManager.LOG);
}
this.enrichMonitor= null;
this.monitor= null;
this.workerThread= null;
terminated();
}
protected int calculateTicks() {
try {
int ticks= 0;
String currentContextId= null;
String currentStepId= null;
while (this.operationIterator.next()) {
final DocProcessingOperation operation= this.operationIterator.getOperation();
// new step
final String stepId= this.operationIterator.getStepConfig().getId();
if (currentStepId != stepId) {
ticks+= STEP_TICKS;
currentStepId= stepId;
}
// new context
if (isContextRequired(this.operationIterator)) {
final String contextId= operation.getContextId();
if (contextId != currentContextId) {
ticks+= CONTEXT_TICKS;
currentContextId= contextId;
}
}
ticks+= operation.getTicks();
}
return ticks;
}
finally {
this.operationIterator.reset();
}
}
protected void runProcessing(final SubMonitor progress) {
this.operationIterator.reset();
this.operationIterator.hasNext();
stepChanged(null, this.operationIterator.getStepConfig());
executeOperations();
}
protected void executeOperations() {
while (this.operationIterator.hasNext() && shouldContinue()) {
final String contextId= getNextRequiredContextId();
if (contextId != this.currentContextId) {
if (this.currentContextId == null) { // contextId != null
startNextRequiredContext();
continue;
}
else { // exit current context
return;
}
}
executeNextOperation();
continue;
}
}
private boolean isContextRequired(final DocProcessingToolOperationIterator iterator) {
return (iterator.getOperation().getContextId() != null
|| iterator.getStepPart() == DocProcessingToolOperationIterator.MAIN );
}
private String getNextRequiredContextId() {
if (this.nextContextIterator.compareTo(this.operationIterator) < 0) {
this.nextContextIterator.reset(this.operationIterator);
}
while (this.nextContextIterator.hasNext()) {
if (isContextRequired(this.nextContextIterator)) {
return this.nextContextIterator.getOperation().getContextId();
}
this.nextContextIterator.next(); // not required
}
return null;
}
private void startNextRequiredContext() {
final IStatus status= startContext(getContext(this.nextContextIterator.getOperation()));
if (status.getSeverity() != IStatus.OK) {
log(status);
}
}
protected DocProcessingToolOperationContext getContext(final DocProcessingOperation operation) {
final String id= operation.getContextId();
if (id == null) {
return null;
}
if (this.contexts == null) {
this.contexts= new ArrayList<>(4);
}
else {
for (final DocProcessingToolOperationContext aContext : this.contexts) {
if (id == aContext.getId()) {
return aContext;
}
}
}
final DocProcessingToolOperationContext context= operation.createContext();
if (context == null) {
throw new NullPointerException("context: id= " + id); //$NON-NLS-1$
}
this.contexts.add(context);
return context;
}
protected IStatus startContext(final DocProcessingToolOperationContext context) {
assert (this.currentContext == null);
this.currentContext= context;
this.currentContextId= context.getId();
try {
this.currentContext.start(this, new Runnable() {
@Override
public void run() {
executeOperations();
}
}, this.monitor.newChild(CONTEXT_TICKS, SubMonitor.SUPPRESS_NONE) );
return Status.OK_STATUS;
}
catch (final CoreException e) {
if (e.getStatus().getSeverity() == IStatus.CANCEL) {
return e.getStatus();
}
return new Status(IStatus.ERROR, DocBaseUI.PLUGIN_ID,
NLS.bind(Messages.ProcessingProcess_RunInContext_error_Failed_message,
context.getLabel() ),
e );
}
catch (final Exception e) {
return new Status(IStatus.ERROR, DocBaseUI.PLUGIN_ID,
NLS.bind(Messages.ProcessingProcess_RunInContext_error_UnexpectedError_message,
context.getLabel() ),
e );
}
finally {
this.currentContext= null;
this.currentContextId= null;
}
}
private void executeNextOperation() {
try {
final IStatus status= executeOperation(this.operationIterator.getOperation());
if (status.getSeverity() != IStatus.OK) {
log(status);
}
}
finally {
this.operationIterator.next();
final StepConfig nextStepConfig= this.operationIterator.getStepConfig();
if (nextStepConfig != this.currentStepConfig) {
stepChanged(this.currentStepConfig, nextStepConfig);
}
}
}
protected IStatus executeOperation(final DocProcessingOperation operation) {
final SubMonitor m= this.monitor.newChild(operation.getTicks(), SubMonitor.SUPPRESS_NONE);
try {
return operation.run(this, m);
}
catch (final CoreException e) {
if (e.getStatus().getSeverity() == IStatus.CANCEL) {
return e.getStatus();
}
return new Status(IStatus.ERROR, DocBaseUI.PLUGIN_ID,
NLS.bind(Messages.ProcessingProcess_RunOperation_error_Failed_message,
operation.getLabel(), getCurrentStepLabel() ),
e );
}
catch (final OperationCanceledException e) {
return new Status(IStatus.CANCEL, DocBaseUI.PLUGIN_ID,
NLS.bind(Messages.ProcessingProcess_RunOperation_error_Cancelled_message,
operation.getLabel(), getCurrentStepLabel() ),
e );
}
catch (final Exception e) {
return new Status(IStatus.ERROR, DocBaseUI.PLUGIN_ID,
NLS.bind(Messages.ProcessingProcess_RunOperation_error_UnexpectedError_message,
operation.getLabel(), getCurrentStepLabel() ),
e );
}
finally {
m.done();
}
}
}