| /******************************************************************************* |
| * 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; |
| } |
| } |
| |
| |
| } |