package com.sap.ide.refactoring.core.textual;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.antlr.runtime.Lexer;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import tcs.ConcreteSyntax;
import textblocks.TextBlock;
import com.sap.ide.cts.editor.EditorUtil;
import com.sap.ide.cts.moin.parserfactory.AbstractParserFactory;
import com.sap.ide.refactoring.Activator;
import com.sap.ide.refactoring.core.RefactoringCoreException;
import com.sap.ide.refactoring.core.RefactoringSeverity;
import com.sap.ide.refactoring.core.textual.prettyprint.BatchPrettyPrinter;
import com.sap.ide.refactoring.ui.NamedSubProgressMonitor;
import com.sap.mi.textual.grammar.IModelElementInvestigator;
import com.sap.mi.textual.grammar.impl.ObservableInjectingParser;
import com.sap.mi.textual.parsing.textblocks.TbNavigationUtil;
import com.sap.mi.textual.parsing.textblocks.TbValidationUtil;
import com.sap.mi.textual.textblocks.model.TextBlocksModel;
import com.sap.tc.moin.repository.ModelPartition;
import com.sap.tc.moin.repository.commands.Command;
import com.sap.tc.moin.repository.commands.PartitionOperation;
import com.sap.tc.moin.repository.commands.PartitionOperation.Operation;
import com.sap.tc.moin.repository.mmi.reflect.RefPackage;
import com.sap.tc.moin.textual.moinadapter.adapter.MOINModelAdapter;
/**
* Command used to to synchronize the textual views after model changes.
* Runs the short and the incremental pretty printer.
*
* @author Stephan Erb (d049157)
*/
public class TextBlocksSynchronizationCommand extends Command {
private final Map<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>> textBlocksNeedingPrettyPrinting;
private final Map<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>> textBlocksNeedingShortPrettyPrinting;
private final Map<RootElementTextBlockTuple, TextBlockChange> changePerPrettyPrintedRootTuple = new HashMap<RootElementTextBlockTuple, TextBlockChange>();
private final RefactoringEditorFacade facade;
private final RefactoringStatus synchronizationStatus = new RefactoringStatus();
private final IProgressMonitor pm;
public TextBlocksSynchronizationCommand(RefactoringEditorFacade facade,
Map<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>> textBlocksNeedingPrettyPrinting,
Map<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>> textBlocksNeedingShortPrettyPrinting, IProgressMonitor pm) {
super(facade.getEditorConnection(), "Synchronize TextBlocksModel according to DomainModel changes");
this.facade = facade;
this.textBlocksNeedingPrettyPrinting = textBlocksNeedingPrettyPrinting;
this.textBlocksNeedingShortPrettyPrinting = textBlocksNeedingShortPrettyPrinting;
this.pm = pm;
}
@Override
public boolean canExecute() {
return true;
}
@Override
public void doExecute() {
pm.beginTask("Updating Views:", textBlocksNeedingPrettyPrinting.size() + textBlocksNeedingShortPrettyPrinting.size());
try {
doAllShortPrettyPrinting();
doAllFullPrettyPrinting();
} finally {
pm.done();
}
}
private void doAllShortPrettyPrinting() {
pm.subTask("Short Pretty Printing");
// TODO: we should not try to synchronize the whole model, but only synchronoize
// the individual tokens that have actually changed
// Promises better performance (though that is not a problem at the momen).
for (RootElementTextBlockTuple rootTuple : textBlocksNeedingShortPrettyPrinting.keySet()) {
initializeChangeForRootIfNecessary(rootTuple);
prettyPrintShort(rootTuple.textBlock);
updateChangeToReflectPrettyPrinting(rootTuple, rootTuple.textBlock);
pm.worked(1);
}
}
private void doAllFullPrettyPrinting() {
pm.subTask("Pretty Printing");
BatchPrettyPrinter batchPrettyPrinter = new BatchPrettyPrinter();
// First of all, just calculate which textblocks can re reused.
for (Entry<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>> entry : textBlocksNeedingPrettyPrinting.entrySet()) {
for (ModelElementDocumentNodeChangeDescriptor changeDescriptor : entry.getValue()) {
initializeChangeForRootIfNecessary(entry.getKey());
batchPrettyPrinter.queuePrettyPrintJob(changeDescriptor);
}
}
synchronizationStatus.merge(batchPrettyPrinter.run(new NamedSubProgressMonitor(pm, textBlocksNeedingPrettyPrinting.size())));
for (Entry<RootElementTextBlockTuple, List<ModelElementDocumentNodeChangeDescriptor>> entry : textBlocksNeedingPrettyPrinting.entrySet()) {
for (ModelElementDocumentNodeChangeDescriptor changeDescriptor : entry.getValue()) {
TextBlock prettyPrintedTextBlock = batchPrettyPrinter.getPrettyPrinterResultFor(changeDescriptor);
updateChangeToReflectPrettyPrinting(entry.getKey(), prettyPrintedTextBlock);
}
}
}
private void initializeChangeForRootIfNecessary(RootElementTextBlockTuple rootTuple) {
if (!changePerPrettyPrintedRootTuple.containsKey(rootTuple)) {
TextBlockChange change = new TextBlockChange(facade, rootTuple);
changePerPrettyPrintedRootTuple.put(rootTuple, change);
}
}
private void updateChangeToReflectPrettyPrinting(RootElementTextBlockTuple rootTuple, TextBlock prettyPrintedTextBlock) {
if (!rootTuple.modelElement.is___Alive()) {
return;
}
TextBlockChange change = changePerPrettyPrintedRootTuple.get(rootTuple);
change.fetchPostChangeState(prettyPrintedTextBlock);
}
private void prettyPrintShort(TextBlock rootBlock) {
if (rootBlock.getType() == null || rootBlock.getType().getParseRule() == null) {
Activator.logWarning("Ignoring TextBlock in pretty printing due to broken mapping: " + rootBlock);
return;
}
try {
TextBlocksModel model = new TextBlocksModel(rootBlock, getModelElementInvestigator(rootBlock));
model.doShortPrettyPrintToEditableVersion();
TbValidationUtil.assertTextBlockConsistencyRecursive(TbNavigationUtil.getUltraRoot(rootBlock));
TbValidationUtil.assertCacheIsUpToDate(TbNavigationUtil.getUltraRoot(rootBlock));
} catch (Exception e) {
Activator.logError(e, "Short PrettyPrint failed");
synchronizationStatus.merge(new RefactoringCoreException("Short PrettyPrint failed", e).asRefactoringStatus(RefactoringSeverity.ERROR));
}
}
private IModelElementInvestigator getModelElementInvestigator(TextBlock textBlock) {
ConcreteSyntax syntax = textBlock.getType().getParseRule().getConcretesyntax();
AbstractParserFactory<? extends ObservableInjectingParser, ? extends Lexer> parserFactory = EditorUtil
.constructParserFactoryForSyntax(syntax);
RefPackage metamodelPackage = parserFactory.getMetamodelPackage(getConnection());
return new MOINModelAdapter(metamodelPackage, getConnection(), null, null);
}
@Override
public Collection<PartitionOperation> getAffectedPartitions() {
Collection<RootElementTextBlockTuple> tuples = new ArrayList<RootElementTextBlockTuple>();
tuples.addAll(textBlocksNeedingPrettyPrinting.keySet());
tuples.addAll(textBlocksNeedingShortPrettyPrinting.keySet());
HashSet<ModelPartition> partitionThatWillBeTouched = new HashSet<ModelPartition>();
for (RootElementTextBlockTuple tuple : tuples) {
partitionThatWillBeTouched.add(tuple.textBlock.get___Partition());
partitionThatWillBeTouched.add(tuple.modelElement.get___Partition());
}
Collection<PartitionOperation> partitionOps = new ArrayList<PartitionOperation>();
for (ModelPartition part : partitionThatWillBeTouched) {
partitionOps.add(new PartitionOperation(Operation.EDIT, part.getPri()));
}
return partitionOps;
}
public Collection<TextBlockChange> getTextBlockChanges() {
return changePerPrettyPrintedRootTuple.values();
}
public RefactoringStatus getSynchronizationStatus() {
return synchronizationStatus;
}
}