blob: f716967a8d090d2129611d87046bfd73d54927b7 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}
}