/******************************************************************************* * Copyright (c) 2005, 2012 IBM Corporation 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.bpel.ui.util; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.bpel.common.extension.model.ExtensionMap; import org.eclipse.bpel.model.Activity; import org.eclipse.bpel.model.CorrelationSet; import org.eclipse.bpel.model.PartnerLink; import org.eclipse.bpel.model.Source; import org.eclipse.bpel.model.Sources; import org.eclipse.bpel.model.Target; import org.eclipse.bpel.model.Targets; import org.eclipse.bpel.model.Variable; import org.eclipse.bpel.model.resource.BPELResource; import org.eclipse.bpel.model.resource.BPELWriter; import org.eclipse.bpel.model.util.BPELConstants; import org.eclipse.bpel.ui.adapters.IContainer; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.widgets.Display; /** * The transfer buffer is the de-facto clipboard that us being used by the BPEL editor. * * Each BPEL editor has its own transfer buffer. The objects in the transfer buffer * are just clones of the selected EMF objects. A little post processing is done to make * sure that in the case of a multiple selection only the top most objects of any tree * are in the selection (so if you select "empty" and a "sequence" in which the "empty" * resides then only "sequence" is copied to the transfer buffer. * * In addition to coping the objects to the transfer buffer a serialized version of the * them is dumped into the system clipboard as BPEL XML. This allows for cross-editor copy/paste * that works reasonably well. * * It also allows for valid BPEL XML to be pasted into the editor from other (textual) representations. * * * @author IBM, Original contribution. * @author Michal Chmielewski (michal.chmielewski@oracle.com) * @date Jun 4, 2007 * */ @SuppressWarnings("nls") public class TransferBuffer { protected static final boolean DEBUG = false; static final String NL = System.getProperty("line.separator"); /** * @author IBM, Original contribution. * @author Michal Chmielewski (michal.chmielewski@oracle.com) * @date Jun 4, 2007 * */ public class Contents { Map<EObject, EObject> fExtensionMap; /** The list of root objects in our transfer buffer */ List<EObject> fRootObjects; /** The textual content of the clipboard */ String fText; Contents (Map<EObject, EObject> extensionMap, List<EObject> rootList ) { fExtensionMap = extensionMap; fRootObjects = rootList; } @SuppressWarnings("nls") void transferToClipboard () { // Populate the clipboard with the XML version of this object list. if (fRootObjects.size() == 1) { EObject ref = fRootObjects.get(0); fText = fWriter.toXML ( ref ); } else if (fRootObjects.size() > 1) { StringBuilder builder = new StringBuilder(); builder.append("<bag>").append("\n\n"); for(EObject obj : fRootObjects) { builder.append(fWriter.toXML ( obj )); } builder.append("\n</bag>"); fText = builder.toString(); } // System.out.println("BPEL Source: " + fClipboardText); /** Clipboard clipboard = new Clipboard(display); * String textData = "Hello World"; * String rtfData = "{\\rtf1\\b\\i Hello World}"; * TextTransfer textTransfer = TextTransfer.getInstance(); * RTFTransfer rtfTransfer = RTFTransfer.getInstance(); * Transfer[] transfers = new Transfer[]{textTransfer, rtfTransfer}; * Object[] data = new Object[]{textData, rtfData}; * clipboard.setContents(data, transfers, DND.CLIPBOARD); * clipboard.dispose(); * * Object[] data, Transfer[] dataTypes */ fClipboard.setContents(new Object[] { fText } , new Transfer[] { TextTransfer.getInstance() }) ; } } /** The system clipboard */ Clipboard fClipboard = null; /** The model BPEL Reader that we use to read the content from a clipboard (text paste) */ org.eclipse.bpel.model.resource.BPELReader fReader = null; /** The model BPEL Writer that we use to serialize the content */ BPELWriter fWriter = null; /** The current contents of the transfer buffer */ Contents fContents; /** The target resource */ BPELResource fTargetResource; /** * Brand new shiny transfer buffer with clipboard support. * * @param display */ public TransferBuffer ( Display display ) { fClipboard = new Clipboard(display); fWriter = new BPELWriter() { @Override public BPELResource getResource() { return fTargetResource; } }; fReader = new org.eclipse.bpel.model.resource.BPELReader () { @Override public Resource getResource() { return fTargetResource; } }; } /** * Return the contents of the transfer buffer. * * @return contents of the transfer buffer. */ public Contents getContents() { return fContents; } /** * Set the contents of this transfer buffer. * * This happens from the BPEL editor side, that is, when the Copy command is issued. * * @param aContents contents of this transfer buffer. */ @SuppressWarnings("nls") void setContents (Contents aContents) { fContents = aContents; } String getClipboardText () { return (String) fClipboard.getContents( TextTransfer.getInstance() ); } /** * Get the list of outermost objects from the list of objects passed. * If a sequence and an activity in the sequence are present in aList, then only * the sequence is returned. * * @param aList * @return the list of outermost objects. */ List<EObject> getOutermostObjects(List<EObject> aList) { ArrayList<EObject> trimmedList = new ArrayList<EObject>(aList.size()); for (EObject next : aList) { boolean skipNext = false; for (EObject parent : aList) { if (next != parent && ModelHelper.isChildContainedBy(parent, next)) { skipNext = true; break; } } if (skipNext) { continue; } trimmedList.add(next); } return trimmedList; } /** * Copy the passed source objects to the transfer buffer. * * * @param sourceObjects * @param sourceMap */ public void copyObjectsToTransferBuffer (List<EObject> sourceObjects, ExtensionMap sourceMap) { if (DEBUG) { System.out .println("copyObjectsToTransferBuffer(" + sourceObjects.size() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ } Map<EObject, EObject> targetMap = new HashMap<EObject, EObject>(); List<EObject> sourceList = getOutermostObjects (sourceObjects); List<EObject> targetList = new ArrayList<EObject>(); // TODO: are there issues here with processing subtrees one-by-one? // E.g. references // to an object which is copied in a different subtree? (Probably not, // for our model) for (EObject source : sourceList) { EObject target = BPELUtil .cloneSubtree(source, sourceMap, targetMap).targetRoot; targetList.add(target); } // Remove links which are referenced by root activities and were not // copied! Otherwise, when the root activities are pasted the stale // references from // roots to these links will be pasted too. for ( EObject next : targetList ) { if (next instanceof Activity == false) { continue; } Activity activity = (Activity) next; Sources sources = activity.getSources(); if (sources != null) { for(Source source : sources.getChildren() ) { if (!targetMap.containsValue(source.getLink())) { source.setLink(null); sources.getChildren().remove(source); } } if (sources.getChildren().isEmpty()) { activity.setSources(null); } } Targets targets = activity.getTargets(); if (targets != null) { for (Target target : targets.getChildren() ) { if (!targetMap.containsValue(target.getLink())) { target.setLink(null); targets.getChildren().remove(target); } } if (targets.getChildren().isEmpty()) { activity.setTargets(null); } } } /** * This has to have a better way of being computed. * The transfer buffer belongs to the editor and an editor edits one resource. So this should be an invariant * over the lifetime of the transfer buffer. * */ if (fTargetResource == null) { fTargetResource = (BPELResource) ModelHelper.getBPELEditor( sourceList.get(0) ).getResource(); } Contents contents = new Contents( targetMap,targetList ); contents.transferToClipboard(); setContents( contents ); } /** * Copy the transfer buffer to the targetObject. * * @param targetObject * the target object to act as an anchor point. * * @param targetMap * @param bReference treat target as reference. * * @return the list of new objects added. */ @SuppressWarnings("nls") public List<EObject> copyTransferBuffer (EObject targetObject, ExtensionMap targetMap, boolean bReference ) { String xml = getClipboardText(); // First check if we need to copy from clipboard. if (fContents == null || xml.equals(fContents.fText) == false ) { if (couldBeXML(xml) == false) { setContents( null ); } else { if (fTargetResource == null) { // This has to have a better way of being computed. fTargetResource = (BPELResource) ModelHelper.getBPELEditor( targetObject ).getResource(); } List<EObject> result = fReader.fromXML( adjustXMLSource(xml) , "Clipboard", fTargetResource ); if (result.size() > 0) { setContents( new Contents(null,result) ); } else { setContents( null ); } } } Anchors anchors = getAnchors (targetObject,bReference,fContents); return copyContentsTo (fContents, anchors, targetMap); } List<EObject> copyContentsTo ( Contents contents, Anchors anchors, ExtensionMap targetMap ) { ArrayList<EObject> newObjects = new ArrayList<EObject>(); if (contents == null) { return newObjects; } for (EObject source : contents.fRootObjects) { BPELUtil.CloneResult cloneResult = BPELUtil.cloneSubtree(source,contents.fExtensionMap, targetMap); anchors.fContainer.addChild(anchors.fTarget, cloneResult.targetRoot, anchors.fRefObject ); // Resolve name of the source activity to be unique if (source instanceof Activity) { Activity node = (Activity) cloneResult.targetRoot; String uniqueName = BPELUtil.generateUniqueModelName ( anchors.fTarget, node.getName(), node ); node.setName(BPELUtil.upperCaseFirstLetter (uniqueName) ); } else if (source instanceof Variable) { Variable node = (Variable) cloneResult.targetRoot; String uniqueName = BPELUtil.generateUniqueModelName ( anchors.fTarget, node.getName(), node ); node.setName(uniqueName); } else if (source instanceof PartnerLink) { PartnerLink node = (PartnerLink) cloneResult.targetRoot; String uniqueName = BPELUtil.generateUniqueModelName ( anchors.fTarget, node.getName(), node ); node.setName(uniqueName); } else if (source instanceof CorrelationSet ) { CorrelationSet node = (CorrelationSet) cloneResult.targetRoot; String uniqueName = BPELUtil.generateUniqueModelName (anchors.fTarget, node.getName(), node ); node.setName(uniqueName); } newObjects.add(cloneResult.targetRoot); } return newObjects; } class Anchors { EObject fTarget; EObject fRefObject; IContainer<EObject> fContainer; } /** * Anchors just represent the anchor points for the paste operation. * * There are several possibilities to consider. * * <ol> * <li> The */ Anchors getAnchors ( EObject targetObject, boolean bReference, Contents contents ) { Anchors anchors = new Anchors(); anchors.fTarget = targetObject; anchors.fContainer = BPELUtil.adapt(anchors.fTarget, IContainer.class); /** * If we are not a container, then we presume that a container is our parent * (such an an activity and its container being say a sequence). * * Also, if bReference is forced, then were a reference object (the insertion point) * and so we must go to our container. */ if (anchors.fContainer == null || bReference ) { // check its container anchors.fRefObject = targetObject; anchors.fTarget = targetObject.eContainer(); anchors.fContainer = BPELUtil.adapt(anchors.fTarget, IContainer.class); } else { /** Otherwise we are container. * * But can we take the contents that is given to us ? */ if (canCopyContents(anchors, contents) == true) { return anchors; } /** Otherwise we assume that we are referenced container*/ anchors.fRefObject = targetObject; anchors.fTarget = targetObject.eContainer(); anchors.fContainer = BPELUtil.adapt(anchors.fTarget, IContainer.class); } return anchors; } boolean canCopyContents ( Anchors anchors, Contents content ) { if (content == null) { return false; } // check each root object's type against the container.. for (EObject next : content.fRootObjects ) { if (anchors.fContainer.canAddObject(anchors.fTarget, next, anchors.fRefObject) ) { return true; } } return false; } /** * * * @param targetObject * the target reference object around which the copy-from-buffer * should be made. * @param bReference - treat the target as a reference, even if it is a container. * @return true of copy of transfer buffer can be made, false otherwise. */ @SuppressWarnings("nls") public boolean canCopyTransferBufferTo (EObject targetObject, boolean bReference ) { if (targetObject == null ) { return false; } Anchors anchors = getAnchors (targetObject,bReference, fContents ); if (anchors.fContainer == null) { return false; } return canCopyContents ( anchors, fContents ) || couldBeXML(getClipboardText()) ; } /** * This is a completely heuristic test to see if we have something in XML in the clipboard * that "could" be converted into objects in our little world. * * * * @param xml * @return */ boolean couldBeXML (String xml) { if (xml == null || xml.length() < 4) { return false; } // Short valid XML element would be .... ? // <f/> int nOpen = 0; int nClose = 0; int nNonWhitespace = 0; int nWhitespace = 0; /** open and close should match, we count them, and then make sure we have about 0.95 ratio */ for (char ch : xml.toCharArray() ) { if (Character.isWhitespace(ch)) { nWhitespace += 1; } else if (ch == '<') { // leading non-whitespace if (nOpen == 0 && nNonWhitespace > 0) { return false; } nOpen += 1; } else if (ch == '>') { nClose += 1; } else { nNonWhitespace += 1; } } if (nOpen == 0 || nClose == 0) { return false; } float ratio = (float) (Math.min(nOpen, nClose) * 1.0 / 1.0 * Math.max(nOpen,nClose)); if (ratio < 0.95) { return false; } return true; } /** Any namespace declaration, either xmlns="" or pfx:xmlns="xx" */ static Pattern anyNamespace = Pattern.compile("(\\:|\\s)xmlns=(\\\"|\\')", Pattern.MULTILINE ); /** BPEL 2004 namespace, as default (no prefix mapping) */ static Pattern bpel2004DefaultNS = Pattern.compile("\\sxmlns=(\"|\')" + Pattern.quote(BPELConstants.NAMESPACE_2004) + "(\"|\')", Pattern.MULTILINE); /** BPEL 2003 namespace, as default (no prefix mapping) */ static Pattern bpel2003DefaultNS = Pattern.compile("\\sxmlns=(\"|\')" + Pattern.quote(BPELConstants.NAMESPACE_2003) + "(\"|\')", Pattern.MULTILINE); static String EMPTY_STRING = ""; /** * Adjust XML source so that we can parse it as BPEL 2.0 * * @param buffer the current XML source. * @return the adjust source. */ static public String adjustXMLSource ( String buffer ) { /** Check if no Namespaces at all. Then there are no prefixes that are namespace bound */ Matcher matcher = anyNamespace.matcher(buffer); if (matcher.find() == false) { StringBuilder sb = new StringBuilder(buffer.length() + 128); sb.append("<bag xmlns=\"").append( BPELConstants.NAMESPACE ).append("\">").append(NL); sb.append(buffer); sb.append(NL).append("</bag>"); return sb.toString(); } /** Check if there is a BPELNamespace source mapping in the buffer. */ /** Check pre-2.0 namespaces as well and replace them by the 2.0 BPEL namespace */ matcher = bpel2004DefaultNS.matcher(buffer); if ( matcher.find() ) { return adjustXMLSource(matcher.replaceAll( EMPTY_STRING )) ; } matcher = bpel2003DefaultNS.matcher(buffer); if ( matcher.find() ) { return adjustXMLSource ( matcher.replaceAll( EMPTY_STRING ) ); } /** Return buffer as is */ return buffer; } /** * */ public void dispose() { setContents(null); if (fClipboard != null) { fClipboard.dispose(); fClipboard = null; } } }