/*******************************************************************************
* Copyright (c) 2014, 2015 Cisco Systems, Inc. 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
*
*******************************************************************************/
package com.cisco.yangide.ext.model.editor.sync;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.eclipse.core.resources.IFile;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.EMFCompare;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.merge.BatchMerger;
import org.eclipse.emf.compare.merge.IBatchMerger;
import org.eclipse.emf.compare.merge.IMerger;
import org.eclipse.emf.compare.merge.ReferenceChangeMerger;
import org.eclipse.emf.compare.scope.DefaultComparisonScope;
import org.eclipse.emf.compare.scope.IComparisonScope;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
import org.eclipse.swt.widgets.Display;
import org.eclipse.text.undo.DocumentUndoEvent;
import org.eclipse.text.undo.DocumentUndoManagerRegistry;
import org.eclipse.text.undo.IDocumentUndoListener;
import org.eclipse.ui.IFileEditorInput;
import com.cisco.yangide.core.YangCorePlugin;
import com.cisco.yangide.core.YangModelException;
import com.cisco.yangide.core.dom.ASTNode;
import com.cisco.yangide.core.parser.YangParserUtil;
import com.cisco.yangide.editor.editors.IReconcileHandler;
import com.cisco.yangide.editor.editors.YangEditor;
import com.cisco.yangide.ext.model.Module;
import com.cisco.yangide.ext.model.Node;
import com.cisco.yangide.ext.model.editor.Activator;
import com.cisco.yangide.ext.model.editor.editors.IModelChangeHandler;
import com.cisco.yangide.ext.model.editor.editors.ISourceModelManager;
import com.cisco.yangide.ext.model.editor.editors.YangDiagramEditor;
import com.cisco.yangide.ext.model.editor.util.YangModelUtil;
import com.cisco.yangide.ext.refactoring.code.ExtractGroupingRefactoring;
import com.cisco.yangide.ext.refactoring.ui.ExtractGroupingRefactoringWizard;
/**
* @author Konstantin Zaitsev
* @date Aug 13, 2014
*/
public class ModelSynchronizer implements IDocumentUndoListener, IReconcileHandler {
private YangEditor yangSourceEditor;
// modules to sync
private com.cisco.yangide.core.dom.Module astModule;
private Module diagModule;
// source to diag notifier
private IModelChangeHandler modelChangeHandler;
private DiagramModelMergeAdapter diagModelMergeAdapter;
/** Diag node to AST node mapping. */
private Map<Node, ASTNode> mapping = new WeakHashMap<>();
private boolean notification;
private boolean sourceInvalid;
private DiagramModelAdapter diagModelAdapter;
private ISourceModelManager sourceModelManager = new ISourceModelManager() {
@Override
public void createSourceElement(Node parent, int position, String content) {
ASTNode node = mapping.get(parent);
diagModelAdapter.add(node, content + System.lineSeparator(), position);
syncWithSource();
}
@Override
public void extractGrouping(List<Node> nodes) {
int startPosition = Integer.MAX_VALUE;
int endPosition = Integer.MIN_VALUE;
for (Node node : nodes) {
ASTNode astNode = mapping.get(node);
startPosition = Math.min(startPosition, astNode.getStartPosition());
endPosition = Math.max(endPosition, astNode.getEndPosition() + 1);
}
IFile file = ((IFileEditorInput) yangSourceEditor.getEditorInput()).getFile();
try {
ExtractGroupingRefactoring refactoring = new ExtractGroupingRefactoring(file,
yangSourceEditor.getModule(), startPosition, endPosition - startPosition);
ExtractGroupingRefactoringWizard wizard = new ExtractGroupingRefactoringWizard(refactoring);
RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard);
op.run(Display.getDefault().getActiveShell(), "Extract Grouping");
syncWithSource();
} catch (InterruptedException | YangModelException e) {
// do nothing
}
}
@Override
public ASTNode getModuleNode(Node node) {
return mapping.get(node);
}
};
public ModelSynchronizer(YangEditor yangSourceEditor, YangDiagramEditor yangDiagramEditor) {
this.yangSourceEditor = yangSourceEditor;
this.modelChangeHandler = yangDiagramEditor.getModelChangeHandler();
this.diagModelMergeAdapter = new DiagramModelMergeAdapter(modelChangeHandler);
this.diagModelAdapter = new DiagramModelAdapter(this, yangSourceEditor, mapping);
}
public void init() {
this.yangSourceEditor.addReconcileHandler(this);
DocumentUndoManagerRegistry.getDocumentUndoManager(yangSourceEditor.getDocument())
.addDocumentUndoListener(this);
}
public void dispose() {
this.yangSourceEditor.removeReconcileHandler(this);
}
public void disableNotification() {
notification = false;
}
public void enableNotification() {
notification = true;
}
public boolean isNotificationEnabled() {
return notification;
}
public void syncWithSource(com.cisco.yangide.core.dom.Module module) {
if (isNotificationEnabled()) {
try {
disableNotification();
updateFromSource(module, true);
} finally {
enableNotification();
}
}
}
public void syncWithSource() {
if (isNotificationEnabled()) {
try {
disableNotification();
char[] content = yangSourceEditor.getDocument().get().toCharArray();
com.cisco.yangide.core.dom.Module module = YangParserUtil.parseYangFile(content);
setSourceInvalid(!module.isSyntaxValid());
if (module.isSyntaxValid()) {
updateFromSource(module, true);
}
} finally {
enableNotification();
}
}
}
public ISourceModelManager getSourceModelManager() {
return sourceModelManager;
}
void updateFromSource(com.cisco.yangide.core.dom.Module module, boolean notify) {
if (Activator.getDefault().isDebugging()) {
System.out.println("from source");
}
final Map<Node, ASTNode> newMapping = new HashMap<>();
astModule = module;
Module moduleNew = YangModelUtil.exportModel(module, newMapping);
IComparisonScope scope = new DefaultComparisonScope(diagModule, moduleNew, diagModule);
Comparison comparison = EMFCompare.builder().build().compare(scope);
updateMappings(newMapping, comparison.getMatches());
List<Diff> differences = comparison.getDifferences();
IMerger.Registry mergerRegistry = IMerger.RegistryImpl.createStandaloneInstance();
ReferenceChangeMerger refMerger = new ReferenceChangeMerger() {
@Override
protected EObject createCopy(EObject referenceObject) {
EObject copy = super.createCopy(referenceObject);
if (newMapping.containsKey(referenceObject) && copy instanceof Node) {
mapping.put((Node) copy, newMapping.remove(referenceObject));
}
return copy;
}
};
refMerger.setRanking(20); // highest rank then default
mergerRegistry.add(refMerger);
IBatchMerger merger = new BatchMerger(mergerRegistry);
if (notify) {
diagModule.eAdapters().add(diagModelMergeAdapter);
}
merger.copyAllRightToLeft(differences, null);
if (notify) {
diagModule.eAdapters().remove(diagModelMergeAdapter);
}
}
private void updateMappings(Map<Node, ASTNode> newMapping, Iterable<Match> matches) {
for (Match match : matches) {
if (newMapping.containsKey(match.getRight())) {
mapping.put((Node) match.getLeft(), newMapping.get(match.getRight()));
}
updateMappings(newMapping, match.getAllSubmatches());
}
}
/**
* @return
*/
public Module getDiagramModule() {
if (diagModule == null) {
diagModule = YangModelUtil.exportModel(getSourceModule(), mapping);
diagModule.eAdapters().add(diagModelAdapter);
}
return diagModule;
}
public com.cisco.yangide.core.dom.Module getSourceModule() {
if (astModule == null) {
try {
astModule = yangSourceEditor.getModule();
if (astModule == null) {
astModule = new com.cisco.yangide.core.dom.Module();
astModule.setName("module");
}
} catch (YangModelException e) {
YangCorePlugin.log(e);
}
}
return astModule;
}
/**
* @return the sourceInvalid
*/
public boolean isSourceInvalid() {
return sourceInvalid;
}
/**
* @param sourceInvalid the sourceInvalid to set
*/
public void setSourceInvalid(boolean sourceInvalid) {
this.sourceInvalid = sourceInvalid;
}
@Override
public void reconcile() {
YangEditor editor = ModelSynchronizer.this.yangSourceEditor;
try {
com.cisco.yangide.core.dom.Module module = editor.getModule();
sourceInvalid = module.getFlags() == ASTNode.MALFORMED;
if (!isSourceInvalid()) {
syncWithSource(module);
}
} catch (YangModelException e) {
YangCorePlugin.log(e);
}
}
@Override
public void documentUndoNotification(DocumentUndoEvent event) {
reconcile();
}
}