package jetbrains.mps.ide.datatransfer; /*Generated by MPS */ import org.apache.log4j.Logger; import org.apache.log4j.LogManager; import java.util.Set; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.language.SLanguage; import java.util.Map; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SReference; import jetbrains.mps.datatransfer.PasteNodeData; import java.util.List; import jetbrains.mps.internal.collections.runtime.ListSequence; import org.jetbrains.mps.openapi.model.SModel; import jetbrains.mps.internal.collections.runtime.MapSequence; import java.util.HashMap; import jetbrains.mps.internal.collections.runtime.SetSequence; import java.util.HashSet; import jetbrains.mps.datatransfer.DataTransferManager; import jetbrains.mps.internal.collections.runtime.ISelector; import java.util.ArrayList; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.language.SProperty; import jetbrains.mps.internal.collections.runtime.Sequence; import jetbrains.mps.lang.smodel.generator.smodelAdapter.AttributeOperations; import org.jetbrains.mps.openapi.language.SContainmentLink; import jetbrains.mps.smodel.StaticReference; import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations; import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory; import com.intellij.ide.CopyPasteManagerEx; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; import java.awt.datatransfer.DataFlavor; import org.apache.log4j.Level; import java.util.Collection; import jetbrains.mps.project.Project; import jetbrains.mps.smodel.SModelOperations; import jetbrains.mps.smodel.language.LanguageRegistry; import jetbrains.mps.smodel.SLanguageHierarchy; import org.jetbrains.annotations.NotNull; import jetbrains.mps.smodel.SModelInternal; import org.jetbrains.mps.openapi.module.SModule; import jetbrains.mps.project.AbstractModule; public final class CopyPasteUtil { private static final Logger LOG = LogManager.getLogger(CopyPasteUtil.class); public CopyPasteUtil() { } private static void processImportsAndLanguages(Set<SModelReference> necessaryImports, Set<SLanguage> necessaryLanguages, Map<SNode, SNode> sourceNodesToNewNodes, Set<SReference> allReferences) { necessaryImports.clear(); necessaryLanguages.clear(); Set<SNode> sourceNodes = sourceNodesToNewNodes.keySet(); for (SNode node : sourceNodes) { necessaryLanguages.add(node.getConcept().getLanguage()); } for (SReference ref : allReferences) { if (sourceNodesToNewNodes.get(ref.getTargetNode()) == null) { SModelReference targetModelReference = ref.getTargetSModelReference(); if (targetModelReference != null) { necessaryImports.add(targetModelReference); } } } } public static PasteNodeData createNodeDataIn(List<SNode> sourceNodes, Map<SNode, Set<SNode>> sourceNodesAndAttributes) { if (ListSequence.fromList(sourceNodes).isEmpty()) { return PasteNodeData.emptyPasteNodeData(null); } SModel model = ListSequence.fromList(sourceNodes).first().getModel(); final Map<SNode, SNode> sourceNodesToNewNodes = MapSequence.fromMap(new HashMap<SNode, SNode>()); Set<SReference> allReferences = SetSequence.fromSet(new HashSet<SReference>()); for (SNode sourceNode : ListSequence.fromList(sourceNodes)) { assert sourceNode.getModel() == model; CopyPasteUtil.copyNode_internal(sourceNode, sourceNodesAndAttributes, sourceNodesToNewNodes, allReferences); } Set<SModelReference> necessaryModels = SetSequence.fromSet(new HashSet<SModelReference>()); Set<SLanguage> necessaryLanguages = SetSequence.fromSet(new HashSet<SLanguage>()); CopyPasteUtil.processImportsAndLanguages(necessaryModels, necessaryLanguages, sourceNodesToNewNodes, allReferences); CopyPasteUtil.processReferencesIn(sourceNodesToNewNodes, allReferences); for (SNode source : ListSequence.fromList(sourceNodes)) { DataTransferManager.getInstance().preProcessNode(MapSequence.fromMap(sourceNodesToNewNodes).get(source), source); } return new PasteNodeData(ListSequence.fromList(sourceNodes).select(new ISelector<SNode, SNode>() { public SNode select(SNode it) { return MapSequence.fromMap(sourceNodesToNewNodes).get(it); } }).toListSequence(), null, check_lwiaog_c0a01a2(model), necessaryLanguages, necessaryModels); } public static PasteNodeData createNodeDataOut(List<SNode> sourceNodes, SModelReference sourceModel, Set<SLanguage> necessaryLanguages, Set<SModelReference> necessaryModels) { if (sourceNodes.isEmpty()) { return PasteNodeData.emptyPasteNodeData(null); } List<SNode> result = new ArrayList<SNode>(); Map<SNode, SNode> sourceNodesToNewNodes = new HashMap<SNode, SNode>(); Set<SReference> allReferences = new HashSet<SReference>(); SModel originalModel = sourceNodes.get(0).getModel(); for (SNode sourceNode : sourceNodes) { assert sourceNode.getModel() == originalModel; SNode nodeToPaste = CopyPasteUtil.copyNode_internal(sourceNode, null, sourceNodesToNewNodes, allReferences); result.add(nodeToPaste); } Set<SReference> referencesRequireResolve = CopyPasteUtil.processReferencesOut(sourceNodesToNewNodes, allReferences); return new PasteNodeData(result, referencesRequireResolve, sourceModel, necessaryLanguages, necessaryModels); } private static SNode copyNode_internal(SNode sourceNode, @Nullable Map<SNode, Set<SNode>> nodesAndAttributes, Map<SNode, SNode> sourceNodesToNewNodes, Set<SReference> allReferences) { SNode targetNode = new jetbrains.mps.smodel.SNode(sourceNode.getConcept(), sourceNode.getNodeId()); for (SProperty name : Sequence.fromIterable(sourceNode.getProperties())) { targetNode.setProperty(name, sourceNode.getProperty(name)); } sourceNodesToNewNodes.put(sourceNode, targetNode); for (SReference reference : sourceNode.getReferences()) { allReferences.add(reference); } for (SNode sourceChild : sourceNode.getChildren()) { if (nodesAndAttributes != null) { if (AttributeOperations.isAttribute(sourceChild)) { Set<SNode> nodes = nodesAndAttributes.get(sourceNode); if (nodes != null && !(nodes.contains(sourceChild))) { continue; } } } SNode targetChild = CopyPasteUtil.copyNode_internal(sourceChild, nodesAndAttributes, sourceNodesToNewNodes, allReferences); SContainmentLink role = sourceChild.getContainmentLink(); assert role != null; targetNode.addChild(role, targetChild); } return targetNode; } private static void processReferencesIn(Map<SNode, SNode> sourceNodesToNewNodes, Set<SReference> allReferences) { for (SReference sourceReference : allReferences) { SNode oldSourceNode = sourceReference.getSourceNode(); SNode newSourceNode = sourceNodesToNewNodes.get(oldSourceNode); SNode oldTargetNode = sourceReference.getTargetNode(); SNode newTargetNode = sourceNodesToNewNodes.get(oldTargetNode); SReference newReference; if (newTargetNode != null) { newReference = jetbrains.mps.smodel.SReference.create(sourceReference.getLink(), newSourceNode, newTargetNode); } else { if (oldTargetNode != null) { // model can be null in case it's generation process and the target node was removed due to in-place transformation // see MPS-24188, this may be fixed when MPS-23902 is fixed SModel model = oldTargetNode.getModel(); newReference = jetbrains.mps.smodel.SReference.create(sourceReference.getLink(), newSourceNode, (model == null ? null : model.getReference()), oldTargetNode.getNodeId()); } else if (((jetbrains.mps.smodel.SReference) sourceReference).getResolveInfo() != null) { newReference = new StaticReference(sourceReference.getLink(), newSourceNode, null, null, ((jetbrains.mps.smodel.SReference) sourceReference).getResolveInfo()); } else { continue; } } newSourceNode.setReference(newReference.getLink(), newReference); } } private static Set<SReference> processReferencesOut(Map<SNode, SNode> sourceNodesToNewNodes, Set<SReference> allReferences) { Set<SReference> referencesRequireResolve = new HashSet<SReference>(); for (SReference sourceReference : allReferences) { SNode oldSourceNode = sourceReference.getSourceNode(); SNode newSourceNode = sourceNodesToNewNodes.get(oldSourceNode); // XXX sourceReference.getTargetNodeReference would suffice, with a bit of refactoring SNode oldTargetNode = sourceReference.getTargetNode(); SNode newTargetNode = sourceNodesToNewNodes.get(oldTargetNode); SReference newReference; if (newTargetNode != null) { newReference = jetbrains.mps.smodel.SReference.create(sourceReference.getLink(), newSourceNode, newTargetNode); } else { // XXX special hack for BL, oh, really? if ((SNodeOperations.isInstanceOf(newSourceNode, MetaAdapterFactory.getInterfaceConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x11857355952L, "jetbrains.mps.baseLanguage.structure.IMethodCall")) || SNodeOperations.isInstanceOf(newSourceNode, MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101de48bf9eL, "jetbrains.mps.baseLanguage.structure.ClassifierType"))) && oldTargetNode != null) { newReference = jetbrains.mps.smodel.SReference.create(sourceReference.getLink(), newSourceNode, oldTargetNode); } else { // XXX the code below is quite suspicious and deserves a refactoring. It seems the point here is to keep resolveInfo of original link, otherwise // SReference.create(newSource, oldTarget) would suffice. Is it our true intention, and is it the smart way to do? If it's common scenario, // why don't we expose it as a distinct #create factory method? String resolveInfo = (oldTargetNode == null ? ((jetbrains.mps.smodel.SReference) sourceReference).getResolveInfo() : oldTargetNode.getName()); if (resolveInfo != null) { if (oldTargetNode != null) { newReference = new StaticReference(sourceReference.getLink(), newSourceNode, oldTargetNode.getReference().getModelReference(), oldTargetNode.getNodeId(), resolveInfo); } else { newReference = new StaticReference(sourceReference.getLink(), newSourceNode, null, null, resolveInfo); } referencesRequireResolve.add(newReference); } else { if (oldTargetNode != null) { newReference = jetbrains.mps.smodel.SReference.create(sourceReference.getLink(), newSourceNode, oldTargetNode); } else { continue; } } } } newSourceNode.setReference(newReference.getLink(), newReference); } return referencesRequireResolve; } public static void copyTextToClipboard(String text) { CopyPasteManagerEx.getInstanceEx().setContents(new StringSelection(text)); } public static void copyTextAndNodeToClipboard(String text, SNode node) { setClipboardContents(new SNodeTransferable(text, node)); } public static void copyNodesAndTextToClipboard(List<SNode> nodes, Map<SNode, Set<SNode>> nodesAndAttributes, String text) { setClipboardContents(new SNodeTransferable(nodes, text, nodesAndAttributes)); } /** * A workaround for the following problem with CopyPasteManagerEx: * * if stringContent of one of existing Transferable instances stored inside CopyPasteManagerEx.myDatas * collection is equals to the stringContent of Transferable we are trying to "push" there (used as a parameter * of this method) then existing element will "float up" inside CopyPasteManagerEx.myDatas collection and will * be used next on next paste operation instead of passed Transferable. * * In case of MPS precondition that string equality of clipboard ontent meant actual equality of passed Trabsferables * (SNodeTransferables) is generally wrong, so we have to work around this logic by deleting all exiting Transferables * to avoid possible collisions between copied elements preventing user from copying actual node under mouse in editor. */ private static void setClipboardContents(Transferable content) { try { String stringContent = getStringContent(content); if (stringContent != null) { for (Transferable existingContent : CopyPasteManagerEx.getInstanceEx().getAllContents()) { if (stringContent.equals(getStringContent(existingContent))) { CopyPasteManagerEx.getInstanceEx().removeContent(existingContent); } } } } catch (UnsupportedFlavorException e) { } catch (IOException ex) { } CopyPasteManagerEx.getInstanceEx().setContents(content); } private static String getStringContent(Transferable content) throws UnsupportedFlavorException, IOException { return (String) content.getTransferData(DataFlavor.stringFlavor); } public static void copyNodesToClipboard(List<SNode> nodes) { StringBuilder stringBuilder = new StringBuilder(); int i = 1; int size = nodes.size(); for (SNode node : nodes) { stringBuilder.append(jetbrains.mps.util.SNodeOperations.getDebugText(node)); if (i < size) { stringBuilder.append("\n"); } i++; } setClipboardContents(new SNodeTransferable(nodes, stringBuilder.toString())); } public static void copyNodeToClipboard(SNode node) { List<SNode> list = new ArrayList<SNode>(); list.add(node); CopyPasteUtil.copyNodesToClipboard(list); } public static List<SNode> getNodesFromClipboard(SModel model) { return CopyPasteUtil.getPasteNodeDataFromClipboard(model).getNodes(); } public static PasteNodeData getPasteNodeDataFromClipboard(SModel model) { Transferable content = null; for (Transferable trf : CopyPasteManagerEx.getInstanceEx().getAllContents()) { if (trf != null && trf.isDataFlavorSupported(SModelDataFlavor.sNode)) { content = trf; } break; } if (content == null) { return PasteNodeData.emptyPasteNodeData(model.getReference()); } if (content.isDataFlavorSupported(SModelDataFlavor.sNode)) { SNodeTransferable nodeTransferable; try { nodeTransferable = (SNodeTransferable) content.getTransferData(SModelDataFlavor.sNode); return nodeTransferable.createNodeData(); } catch (UnsupportedFlavorException e) { if (LOG.isEnabledFor(Level.ERROR)) { LOG.error("Exception", e); } } catch (IOException e) { if (LOG.isEnabledFor(Level.ERROR)) { LOG.error("Exception", e); } } } return PasteNodeData.emptyPasteNodeData(model.getReference()); } public static SNode getNodeFromClipboard(SModel model) { return CopyPasteUtil.getNodesFromClipboard(model).get(0); } @Nullable public static Runnable addImportsWithDialog(final SModel targetModel, final Collection<SLanguage> necessaryLanguages, final Collection<SModelReference> necessaryImports, final Project mpsProject) { if (targetModel.getModule() == null) { return null; } if (mpsProject == null) { return null; } final List<SLanguage> additionalLanguages = new ArrayList<SLanguage>(); final List<SModelReference> additionalModels = new ArrayList<SModelReference>(); mpsProject.getModelAccess().runReadAction(new Runnable() { @Override public void run() { List<SModelReference> allImportedModels = new ArrayList<SModelReference>(); // XXX in fact, allImportedModels doesn't give us implicit imports, while one in necessaryImports may actually be imported already as implicit // need better way to deal with implicit imports. for (SModel sm : SModelOperations.allImportedModels(targetModel)) { allImportedModels.add(sm.getReference()); } // no idea why allImportedModels explicitly removes models from its imports // it's handy for us, though allImportedModels.add(targetModel.getReference()); for (SModelReference modelReference : necessaryImports) { assert modelReference != null; if (!(allImportedModels.contains(modelReference))) { additionalModels.add(modelReference); } } LanguageRegistry langReg = LanguageRegistry.getInstance(mpsProject.getRepository()); Set<SLanguage> allVisibleLanguages = new SLanguageHierarchy(langReg, SModelOperations.getAllLanguageImports(targetModel)).getExtended(); for (SLanguage lang : necessaryLanguages) { if (!(allVisibleLanguages.contains(lang))) { additionalLanguages.add(lang); } } } }); if (additionalModels.isEmpty() && additionalLanguages.isEmpty()) { return null; } AddRequiredImportsDialog dialog = new AddRequiredImportsDialog(mpsProject, additionalModels.toArray(new SModelReference[additionalModels.size()]), additionalLanguages.toArray(new SLanguage[additionalLanguages.size()])); dialog.show(); if (dialog.isOK()) { return addImports(mpsProject, targetModel, dialog.getSelectedLanguages(), dialog.getSelectedImports()); } else { return null; } } @Nullable public static Runnable addImportsWithDialog(PasteNodeData pasteNodeData, SModel targetModel, Project mpsProject) { // shows dialog if necessary and pasted nodes were taken not from the same model SModelReference oldModel = pasteNodeData.getSourceModel(); // no dialog if copying from the same model if (oldModel != null && targetModel.getReference().equals(oldModel)) { return null; } return CopyPasteUtil.addImportsWithDialog(targetModel, pasteNodeData.getNecessaryLanguages(), pasteNodeData.getNecessaryModels(), mpsProject); } private static Runnable addImports(final Project p, final SModel targetModel, @NotNull final SLanguage[] requiredLanguages, @NotNull final SModelReference[] requiredImports) { if (requiredLanguages.length == 0 && requiredImports.length == 0) { return null; } return new Runnable() { @Override public void run() { // model properties for (SModelReference imported : requiredImports) { ((SModelInternal) targetModel).addModelImport(imported, false); } for (SLanguage language : requiredLanguages) { ((SModelInternal) targetModel).addLanguage(language); } // model's module properties SModule targetModule = targetModel.getModule(); if (targetModule == null) { return; } for (SModelReference modelRef : requiredImports) { SModel model = modelRef.resolve(p.getRepository()); if (model == null) { continue; } SModule module = model.getModule(); if (module == null || module == targetModule) { continue; } ((AbstractModule) targetModule).addDependency(module.getModuleReference(), false); } } }; } public static boolean isStringOnTopOfClipboard() { // This method was created in accordance with TextPasteUtil.hasStringInClipboard()/.getStringFromClipboard() // methods we should consider reimplementing these methods in order to iterrate over .getAllContents() collection // in case first available Transferable does not support neither stringFlavor nor sNode one. for (Transferable trf : CopyPasteManagerEx.getInstanceEx().getAllContents()) { if (trf != null) { for (DataFlavor nextFlavor : trf.getTransferDataFlavors()) { if (nextFlavor == SModelDataFlavor.stringFlavor) { return true; } if (nextFlavor == SModelDataFlavor.sNode) { return false; } } } break; } return false; } private static SModelReference check_lwiaog_c0a01a2(SModel checkedDotOperand) { if (null != checkedDotOperand) { return checkedDotOperand.getReference(); } return null; } }