/*******************************************************************************
* 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.Map;
import java.util.WeakHashMap;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.util.EContentAdapter;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.RewriteSessionEditProcessor;
import org.eclipse.swt.widgets.Display;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import com.cisco.yangide.core.YangCorePlugin;
import com.cisco.yangide.core.dom.ASTCompositeNode;
import com.cisco.yangide.core.dom.ASTNamedNode;
import com.cisco.yangide.core.dom.ASTNode;
import com.cisco.yangide.core.dom.BaseReference;
import com.cisco.yangide.core.dom.IdentitySchemaNode;
import com.cisco.yangide.core.dom.Module;
import com.cisco.yangide.core.dom.ModuleImport;
import com.cisco.yangide.core.dom.SimpleNode;
import com.cisco.yangide.core.dom.SubModule;
import com.cisco.yangide.core.parser.YangFormattingPreferences;
import com.cisco.yangide.core.parser.YangParserUtil;
import com.cisco.yangide.editor.editors.YangEditor;
import com.cisco.yangide.editor.editors.YangSourceViewer;
import com.cisco.yangide.ext.model.BelongsTo;
import com.cisco.yangide.ext.model.Import;
import com.cisco.yangide.ext.model.ModelPackage;
import com.cisco.yangide.ext.model.Node;
import com.cisco.yangide.ext.model.Tag;
import com.cisco.yangide.ext.model.editor.Activator;
import com.cisco.yangide.ext.model.editor.util.YangModelUtil;
import com.cisco.yangide.ext.model.editor.util.YangTag;
import com.cisco.yangide.ext.refactoring.RefactorUtil;
/**
* @author Konstantin Zaitsev
* @date Aug 13, 2014
*/
final class DiagramModelAdapter extends EContentAdapter {
private final ModelSynchronizer modelSynchronizer;
private Map<Node, String> removedBlock = new WeakHashMap<>();
private YangEditor yangSourceEditor;
private Map<Node, ASTNode> mapping;
private Map<EClass, SourceNodePropertyUpdater<? extends ASTNode>> propertyUpdaters;
/**
* @param modelSynchronizer
*/
DiagramModelAdapter(ModelSynchronizer modelSynchronizer, YangEditor yangSourceEditor, Map<Node, ASTNode> mapping) {
this.modelSynchronizer = modelSynchronizer;
this.yangSourceEditor = yangSourceEditor;
this.mapping = mapping;
this.propertyUpdaters = new HashMap<>();
// init property updaters
this.propertyUpdaters.put(ModelPackage.Literals.NODE, new SourceNodePropertyUpdater<ASTNode>(this));
this.propertyUpdaters.put(ModelPackage.Literals.MODULE, new ModulePropertyUpdater(this));
this.propertyUpdaters.put(ModelPackage.Literals.SUBMODULE, new ModulePropertyUpdater(this));
this.propertyUpdaters.put(ModelPackage.Literals.CONTAINER, new ContainerPropertyUpdater(this));
this.propertyUpdaters.put(ModelPackage.Literals.LIST, new ListPropertyUpdater(this));
this.propertyUpdaters.put(ModelPackage.Literals.LEAF_LIST, new ListPropertyUpdater(this));
this.propertyUpdaters.put(ModelPackage.Literals.CHOICE, new ChoicePropertyUpdater(this));
this.propertyUpdaters.put(ModelPackage.Literals.TYPEDEF, new TypedefPropertyUpdater(this));
this.propertyUpdaters.put(ModelPackage.Literals.LEAF, new LeafPropertyUpdater(this));
}
@SuppressWarnings("unchecked")
@Override
public synchronized void notifyChanged(Notification notification) {
super.notifyChanged(notification);
if (notification.getEventType() != Notification.REMOVING_ADAPTER) {
if (this.modelSynchronizer.isNotificationEnabled()) {
try {
this.modelSynchronizer.disableNotification();
if (Activator.getDefault().isDebugging()) {
System.out.println("from diag: " + notification);
}
switch (notification.getEventType()) {
case Notification.ADD:
ASTNode node = mapping.get(notification.getNotifier());
if (notification.getNewValue() instanceof Node) {
Node newValue = (Node) notification.getNewValue();
String content = null;
if (removedBlock.containsKey(newValue)) { // block moved from another
// location
if (Activator.getDefault().isDebugging()) {
System.out.println("block moved");
}
content = removedBlock.remove(newValue);
add(node, content, notification.getPosition());
}
if (newValue.eClass() == ModelPackage.Literals.IMPORT) {
addImport((Module) node, newValue);
}
break;
} else if (notification.getNewValue() instanceof Tag) {
if (((Tag) notification.getNewValue()).getValue() != null) {
Activator.logError("tag added with value: " + notification);
}
} else {
Activator.logError("unknown notification : " + notification);
}
case Notification.SET:
if (notification.getFeature() != ModelPackage.Literals.NODE__PARENT) {
// skip notification if value not changed
if (notification.getNewValue() != null && notification.getOldValue() != null
&& notification.getOldValue().equals(notification.getNewValue())) {
break;
}
if (notification.getFeature() == ModelPackage.Literals.NAMED_NODE__NAME
|| notification.getFeature() == ModelPackage.Literals.USES__QNAME) {
updateName((Node) notification.getNotifier(), (EAttribute) notification.getFeature(),
notification.getNewValue());
} else if (notification.getFeature() == ModelPackage.Literals.REFERENCE_NODE__REFERENCE) {
updateIdentityReference((Node) notification.getNotifier(), notification.getNewValue());
} else if (notification.getFeature() == ModelPackage.Literals.SUBMODULE__BELONGS_TO) {
updateBelongsTo((Node) notification.getNotifier(), notification.getOldValue(),
notification.getNewValue());
}
if (notification.getNotifier() instanceof Tag) {
Tag tag = (Tag) notification.getNotifier();
Node parent = (Node) tag.eContainer();
ASTNode astNode = mapping.get(parent);
if (astNode == null) {
throw new RuntimeException(
"Cannot find references source block from diagram editor");
}
if (propertyUpdaters.containsKey(parent.eClass())) {
SourceNodePropertyUpdater<ASTNode> updater = (SourceNodePropertyUpdater<ASTNode>) propertyUpdaters
.get(parent.eClass());
updater.updateProperty(astNode, tag.getName(), notification.getNewValue(),
astNode.getBodyStartPosition() + 1);
} else {
SourceNodePropertyUpdater<ASTNode> updater = (SourceNodePropertyUpdater<ASTNode>) propertyUpdaters
.get(ModelPackage.Literals.NODE);
updater.updateProperty(astNode, tag.getName(), notification.getNewValue(),
astNode.getBodyStartPosition() + 1);
}
}
}
break;
case Notification.REMOVE:
if (notification.getOldValue() != null && notification.getOldValue() instanceof Node
&& mapping.containsKey(notification.getOldValue())) {
delete((Node) notification.getOldValue());
}
break;
case Notification.MOVE:
if (notification.getFeature() == ModelPackage.Literals.CONTAINING_NODE__CHILDREN) {
move((Node) notification.getNotifier(), (Node) notification.getNewValue(),
(Integer) notification.getOldValue(), notification.getPosition());
}
default:
break;
}
} finally {
this.modelSynchronizer.enableNotification();
}
}
}
}
private void addImport(Module module, Node newValue) {
int position = 0;
if (module.getImports().isEmpty()) {
if (module.getPrefix() != null) {
position = module.getPrefix().getEndPosition() + 1;
} else {
position = module.getBodyStartPosition() + 1;
}
} else {
for (ASTNode astNode : module.getChildren()) {
if (astNode instanceof ModuleImport) {
position = astNode.getEndPosition() + 1;
}
}
}
performEdit(new InsertEdit(position, System.lineSeparator() + formatImport((Import) newValue)));
}
/**
* @param notifier
* @param newValue
* @param oldIntValue
* @param position
*/
private void move(Node notifier, Node newValue, int oldPosition, int position) {
ASTNode node = mapping.get(notifier);
ASTNode child = mapping.get(newValue);
if (!(node instanceof ASTCompositeNode)) {
throw new RuntimeException("Parent node should be composite");
}
ASTCompositeNode parent = (ASTCompositeNode) node;
int insertPosition = parent.getBodyStartPosition() + 2;
if (0 != position && parent.getChildren().size() > 0) {
int size = parent.getChildren().size();
insertPosition = position < 0 || position >= size ? parent.getChildren().get(size - 1).getEndPosition() + 2
: parent.getChildren().get(
parent.getChildren().contains(child) && position < oldPosition ? position - 1 : position)
.getEndPosition() + 2;
}
try {
String content = yangSourceEditor.getDocument().get(child.getStartPosition(), child.getLength() + 1);
TextEdit composite = new MultiTextEdit();
composite.addChild(new DeleteEdit(child.getStartPosition(), child.getLength() + 1));
composite.addChild(new InsertEdit(insertPosition, content));
performEdit(composite);
} catch (BadLocationException e) {
YangCorePlugin.log(e);
}
}
public void add(ASTNode node, String content, int position) {
if (!(node instanceof ASTCompositeNode)) {
throw new RuntimeException("Parent node should be composite");
}
ASTCompositeNode parent = (ASTCompositeNode) node;
int insertPosition = parent.getBodyStartPosition() + 2;
if (0 != position && parent.getChildren().size() > 0) {
int size = parent.getChildren().size();
insertPosition = position < 0 || position >= size ? parent.getChildren().get(size - 1).getEndPosition() + 2
: parent.getChildren().get(position - 1).getEndPosition() + 2;
}
if (node instanceof Module || node instanceof SubModule) {
insertPosition = parent.getBodyEndPosition() - 1;
}
String formattedContent = YangParserUtil.formatYangSource(new YangFormattingPreferences(),
content.toCharArray(), getIndentLevel(node), System.getProperty("line.separator"));
performEdit(new InsertEdit(insertPosition, formattedContent));
}
void delete(Node node) {
ASTNode astNode = mapping.get(node);
try {
removedBlock.put(node,
yangSourceEditor.getDocument().get(astNode.getStartPosition(), astNode.getLength() + 1));
} catch (BadLocationException e) {
// ignore exception
}
performEdit(new DeleteEdit(astNode.getStartPosition(), astNode.getLength() + 1));
}
void updateName(Node node, EAttribute feature, Object newValue) {
ASTNode astNode = mapping.get(node);
if (astNode == null) {
throw new RuntimeException("Cannot find references source block from diagram editor");
}
if (!(astNode instanceof ASTNamedNode)) {
throw new RuntimeException("Source block is not named element");
}
ASTNamedNode nnode = (ASTNamedNode) astNode;
performEdit(new ReplaceEdit(nnode.getNameStartPosition(), nnode.getNameLength(), (String) newValue));
}
private void updateIdentityReference(Node node, Object newValue) {
ASTNode astNode = mapping.get(node);
if (astNode == null) {
throw new RuntimeException("Cannot find references source block from diagram editor");
}
BaseReference base = ((IdentitySchemaNode) astNode).getBase();
if (base == null && newValue != null) {
performEdit(new InsertEdit(astNode.getBodyStartPosition() + 1, formatBase(astNode, (String) newValue)));
} else if (base != null) {
if (newValue != null && !((String) newValue).trim().isEmpty()) {
performEdit(new ReplaceEdit(base.getNameStartPosition(), base.getNameLength(), (String) newValue));
} else {
performEdit(new DeleteEdit(base.getStartPosition(), base.getLength() + 1));
}
}
}
private void updateBelongsTo(Node node, Object oldValue, Object newValue) {
ASTNode astNode = mapping.get(node);
if (astNode == null) {
throw new RuntimeException("Cannot find references source block from diagram editor");
}
com.cisco.yangide.core.dom.SubModule subModule = (com.cisco.yangide.core.dom.SubModule) astNode;
SimpleNode<String> btNode = subModule.getParentModule();
if (btNode != null) {
performEdit(new ReplaceEdit(btNode.getStartPosition(), btNode.getLength() + 1,
formatBelongsTo((BelongsTo) newValue)));
} else {
performEdit(new InsertEdit(subModule.getBodyStartPosition() + 1,
System.lineSeparator() + formatBelongsTo((BelongsTo) newValue)));
}
}
synchronized void performEdit(final TextEdit edit) {
Display display = Display.getCurrent();
if (display == null) {
display = Display.getDefault();
}
display.syncExec(new Runnable() {
@Override
public void run() {
try {
YangSourceViewer viewer = (YangSourceViewer) yangSourceEditor.getViewer();
MultiTextEdit mt = new MultiTextEdit();
mt.addChild(edit);
new RewriteSessionEditProcessor(viewer.getDocument(), mt, TextEdit.CREATE_UNDO).performEdits();
viewer.updateDocument();
} catch (MalformedTreeException | BadLocationException e) {
Activator.log(e, e.getMessage());
}
}
});
char[] content = yangSourceEditor.getDocument().get().toCharArray();
com.cisco.yangide.core.dom.Module module = YangParserUtil.parseYangFile(content);
modelSynchronizer.updateFromSource(module, true);
yangSourceEditor.reconcileModel();
}
public int getIndentLevel(ASTNode node) {
int level = 0;
ASTNode parent = node;
while (parent != null) {
parent = parent.getParent();
level++;
}
return level;
}
private String formatBase(ASTNode node, String value) {
return trimTrailingSpaces(
RefactorUtil.formatCodeSnipped("\nbase " + empty2Quote(value) + ";", getIndentLevel(node)));
}
private String formatImport(Import newValue) {
StringBuilder sb = new StringBuilder();
sb.append("import ").append(newValue.getModule()).append(" {\n");
sb.append("prefix ").append(newValue.getPrefix()).append(";\n");
sb.append("revision-date \"").append(newValue.getRevisionDate()).append("\";\n");
sb.append("}");
return trimTrailingSpaces(RefactorUtil.formatCodeSnipped(sb.toString(), 1));
}
private String formatBelongsTo(BelongsTo belongsTo) {
com.cisco.yangide.ext.model.Module parentModule = belongsTo.getOwnerModule();
String prefix = (String) YangModelUtil.getValue(YangTag.PREFIX, parentModule);
StringBuilder sb = new StringBuilder();
sb.append("belongs-to ").append(parentModule.getName()).append(" {\n");
sb.append("prefix ").append(prefix).append(";\n");
sb.append("}");
return trimTrailingSpaces(RefactorUtil.formatCodeSnipped(sb.toString(), 0));
}
private String trimTrailingSpaces(String str) {
int len = str.length();
char[] val = str.toCharArray();
while ((len > 0) && (val[len - 1] <= ' ')) {
len--;
}
return (len < str.length()) ? str.substring(0, len) : str;
}
private String empty2Quote(String str) {
return str == null || str.trim().isEmpty() ? ("\"" + str + "\"") : str;
}
}