Target Explorer: Fix file system explorer clipboard handling
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.ui/src/org/eclipse/tcf/te/tcf/filesystem/ui/internal/operations/FsClipboard.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.ui/src/org/eclipse/tcf/te/tcf/filesystem/ui/internal/operations/FsClipboard.java
index d2b186a..e7145bd 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.ui/src/org/eclipse/tcf/te/tcf/filesystem/ui/internal/operations/FsClipboard.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.ui/src/org/eclipse/tcf/te/tcf/filesystem/ui/internal/operations/FsClipboard.java
@@ -11,66 +11,145 @@
 
 import java.beans.PropertyChangeEvent;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
 
+import org.eclipse.core.runtime.Assert;
 import org.eclipse.swt.SWTException;
 import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.dnd.Transfer;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.tcf.te.core.utils.PropertyChangeProvider;
 import org.eclipse.tcf.te.tcf.filesystem.core.interfaces.runtime.IFSTreeNode;
+import org.eclipse.tcf.te.ui.swt.DisplayUtil;
 import org.eclipse.ui.PlatformUI;
 
 /**
  * The clip board to which copy or cut files/folders.
  */
 public class FsClipboard extends PropertyChangeProvider {
-	// The constants to define the current operation type of the clip board.
-	private static final int NONE = -1;
-	private static final int CUT = 0;
-	private static final int COPY = 1;
-	// The operation type, CUT, COPY or NONE.
-	private int operation;
-	// The currently selected files/folders.
-	private List<IFSTreeNode> files;
 
-	private Clipboard clipboard;
+	/* default */ static class FsClipboardContent {
+		// The constants to define the current operation type of the clip board.
+		public static final int CUT = 0;
+		public static final int COPY = 1;
+
+		// The operation type, CUT, COPY or NONE.
+		public final int operation;
+		// The currently selected files/folders.
+		public final List<IFSTreeNode> files;
+
+		/**
+         * Constructor
+         */
+        public FsClipboardContent(int operation, List<IFSTreeNode> files) {
+        	Assert.isTrue(operation == CUT || operation == COPY);
+        	this.operation = operation;
+        	Assert.isNotNull(files);
+        	this.files = files;
+        }
+	}
+
+	/* default */ final Clipboard clipboard;
 
 	/**
 	 * Create a clip board instance.
 	 */
 	public FsClipboard() {
 		clipboard = new Clipboard(PlatformUI.getWorkbench().getDisplay());
-		operation = NONE;
 	}
 
 	public boolean isCutOp() {
-		return operation == CUT;
+		final AtomicReference<Object> object = new AtomicReference<Object>();
+
+		Runnable runnable = new Runnable() {
+			@Override
+			public void run() {
+				object.set(clipboard.getContents(FsClipboardTransfer.getInstance()));
+			}
+		};
+
+		exec(runnable);
+
+		FsClipboardContent content = (FsClipboardContent) object.get();
+
+		return content != null && content.operation == FsClipboardContent.CUT;
 	}
 
 	public boolean isCopyOp() {
-		return operation == COPY;
+		final AtomicReference<Object> object = new AtomicReference<Object>();
+
+		Runnable runnable = new Runnable() {
+			@Override
+			public void run() {
+				object.set(clipboard.getContents(FsClipboardTransfer.getInstance()));
+			}
+		};
+
+		exec(runnable);
+
+		FsClipboardContent content = (FsClipboardContent) object.get();
+
+		return content != null && content.operation == FsClipboardContent.COPY;
 	}
 
 	public boolean isEmpty() {
-		return operation == NONE && (files == null || files.isEmpty());
+		final AtomicReference<Object> object = new AtomicReference<Object>();
+
+		Runnable runnable = new Runnable() {
+			@Override
+			public void run() {
+				object.set(clipboard.getContents(FsClipboardTransfer.getInstance()));
+			}
+		};
+
+		exec(runnable);
+
+		FsClipboardContent content = (FsClipboardContent) object.get();
+
+		return content == null || content.files.isEmpty();
 	}
 
 	/**
 	 * Get the currently selected files/folders to operated.
 	 */
 	public List<IFSTreeNode> getFiles() {
-		return files;
+		final AtomicReference<Object> object = new AtomicReference<Object>();
+
+		Runnable runnable = new Runnable() {
+			@Override
+			public void run() {
+				object.set(clipboard.getContents(FsClipboardTransfer.getInstance()));
+			}
+		};
+
+		exec(runnable);
+
+		FsClipboardContent content = (FsClipboardContent) object.get();
+
+		return content.files;
 	}
 
 	/**
 	 * Cut the specified files/folders to the clip board.
 	 */
 	public void cutFiles(List<IFSTreeNode> files) {
-		operation = CUT;
-		this.files = files;
-		PropertyChangeEvent event = new PropertyChangeEvent(this, "cut", null, null); //$NON-NLS-1$
-		firePropertyChange(event);
+		Assert.isNotNull(files);
 
-		clearSystemClipboard();
+		final FsClipboardContent content = new FsClipboardContent(FsClipboardContent.CUT, files);
+		final FsClipboardTransfer transfer = FsClipboardTransfer.getInstance();
+		transfer.setContent(content);
+
+		Runnable runnable = new Runnable() {
+			@Override
+			public void run() {
+				clipboard.setContents(new Object[] { content }, new Transfer[] { transfer });
+
+				PropertyChangeEvent event = new PropertyChangeEvent(this, "cut", null, null); //$NON-NLS-1$
+				firePropertyChange(event);
+			}
+		};
+
+		exec(runnable);
 	}
 
 	/**
@@ -79,70 +158,79 @@
 	 * @param files The file/folder nodes.
 	 */
 	public void copyFiles(List<IFSTreeNode> files) {
-		operation = COPY;
-		this.files = files;
-		PropertyChangeEvent event = new PropertyChangeEvent(this, "copy", null, null); //$NON-NLS-1$
-		firePropertyChange(event);
+		Assert.isNotNull(files);
 
-		clearSystemClipboard();
+		final FsClipboardContent content = new FsClipboardContent(FsClipboardContent.COPY, files);
+		final FsClipboardTransfer transfer = FsClipboardTransfer.getInstance();
+		transfer.setContent(content);
+
+		Runnable runnable = new Runnable() {
+			@Override
+			public void run() {
+				clipboard.setContents(new Object[] { content }, new Transfer[] { transfer });
+
+				PropertyChangeEvent event = new PropertyChangeEvent(this, "copy", null, null); //$NON-NLS-1$
+				firePropertyChange(event);
+			}
+		};
+
+		exec(runnable);
 	}
 
 	/**
 	 * Clear the clip board.
 	 */
 	public void clear() {
-		operation = NONE;
-		this.files = null;
-		PropertyChangeEvent event = new PropertyChangeEvent(this, "clear", null, null); //$NON-NLS-1$
-		firePropertyChange(event);
+		Runnable runnable = new Runnable() {
+			@Override
+			public void run() {
+				clipboard.clearContents();
 
-		clearSystemClipboard();
+				PropertyChangeEvent event = new PropertyChangeEvent(this, "clear", null, null); //$NON-NLS-1$
+				firePropertyChange(event);
+			}
+		};
+
+		exec(runnable);
 	}
 
 	/**
-	 * Make sure the system clip board is cleared in a UI thread.
+	 * Executes the given runnable in the UI thread synchronously.
+	 *
+	 * @param runnable The runnable. Must not be <code>null</code>.
 	 */
-	void clearSystemClipboard() {
+	private void exec(Runnable runnable) {
+		Assert.isNotNull(runnable);
+
 		if (Display.getCurrent() != null) {
-			clipboard.clearContents();
-		}
-		else {
-			PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable(){
-				@Override
-                public void run() {
-					clearSystemClipboard();
-                }});
+			runnable.run();
+		} else {
+			DisplayUtil.safeSyncExec(runnable);
 		}
 	}
 
 	/**
-	 * Dispose the clipboard.
+	 * Dispose the clip board.
 	 */
     public void dispose() {
-		if(Display.getCurrent() != null) {
-			if (!clipboard.isDisposed()) {
-				try {
-					clipboard.dispose();
-				}
-				catch (SWTException e) {
+		Runnable runnable = new Runnable() {
+			@Override
+			public void run() {
+				if (!clipboard.isDisposed()) {
+					try { clipboard.dispose(); } catch (SWTException e) {}
 				}
 			}
-		}
-		else {
-			PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable(){
-				@Override
-                public void run() {
-					dispose();
-                }});
-		}
+		};
+
+		exec(runnable);
 	}
 
 	/**
-	 * Get the system clipboard.
+	 * Get the system clip board.
 	 *
-	 * @return The system clipboard.
+	 * @return The system clip board.
 	 */
-	public Clipboard getSystemClipboard() {
+	public final Clipboard getSystemClipboard() {
 		return clipboard;
 	}
 }
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.ui/src/org/eclipse/tcf/te/tcf/filesystem/ui/internal/operations/FsClipboardTransfer.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.ui/src/org/eclipse/tcf/te/tcf/filesystem/ui/internal/operations/FsClipboardTransfer.java
new file mode 100644
index 0000000..799c06a
--- /dev/null
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.ui/src/org/eclipse/tcf/te/tcf/filesystem/ui/internal/operations/FsClipboardTransfer.java
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Wind River 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
+ *
+ * Contributors:
+ * Wind River Systems - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tcf.te.tcf.filesystem.ui.internal.operations;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.swt.dnd.ByteArrayTransfer;
+import org.eclipse.swt.dnd.TransferData;
+import org.eclipse.tcf.te.tcf.filesystem.ui.activator.UIPlugin;
+import org.eclipse.tcf.te.tcf.filesystem.ui.nls.Messages;
+
+/**
+ * Internal clip board transfer implementation used by the file system clip board.
+ */
+/* default */ class FsClipboardTransfer extends ByteArrayTransfer {
+
+	private static final String TYPE_NAME= "fs-clipboard-transfer-format" + Long.toString(System.currentTimeMillis()); //$NON-NLS-1$;
+	private static final int TYPEID= registerType(TYPE_NAME);
+
+	private FsClipboard.FsClipboardContent content;
+
+	private static class LazyInstance {
+		public static FsClipboardTransfer instance = new FsClipboardTransfer();
+	}
+
+	/**
+	 * Constructor
+	 */
+	/* default */ FsClipboardTransfer() {
+	}
+
+	/**
+	 * Returns the singleton.
+	 */
+	public static FsClipboardTransfer getInstance() {
+		return LazyInstance.instance;
+	}
+
+	/**
+	 * Returns the transfer data.
+	 *
+	 * @return The transfer data or <code>null</code>.
+	 */
+	public FsClipboard.FsClipboardContent getContent() {
+		return content;
+	}
+
+	/**
+	 * Sets the transfer data.
+	 *
+	 * @param content The transfer data or <code>null</code>.
+	 */
+	public void setContent(FsClipboard.FsClipboardContent content) {
+		this.content = content;
+	}
+
+    /* (non-Javadoc)
+     * @see org.eclipse.swt.dnd.Transfer#getTypeIds()
+     */
+    @Override
+	protected int[] getTypeIds() {
+		return new int[] {TYPEID};
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.swt.dnd.Transfer#getTypeNames()
+     */
+    @Override
+	protected String[] getTypeNames() {
+		return new String[] {TYPE_NAME};
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.swt.dnd.Transfer#javaToNative(java.lang.Object, org.eclipse.swt.dnd.TransferData)
+     */
+    @Override
+	protected void javaToNative(Object object, TransferData transferData) {
+		byte[] check= TYPE_NAME.getBytes();
+		super.javaToNative(check, transferData);
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.swt.dnd.Transfer#nativeToJava(org.eclipse.swt.dnd.TransferData)
+     */
+    @Override
+	protected Object nativeToJava(TransferData transferData) {
+		Object result= super.nativeToJava(transferData);
+		if (isInvalidNativeType(result)) {
+            UIPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, UIPlugin.getUniqueIdentifier(), Messages.FsClipboardTransfer_errorMessage));
+		}
+		return content;
+    }
+	/**
+	 * Tests whether native drop data matches this transfer type.
+	 *
+	 * @param result result of converting the native drop data to Java
+	 * @return true if the native drop data does not match this transfer type.
+	 * 	false otherwise.
+	 */
+	private boolean isInvalidNativeType(Object result) {
+		return !(result instanceof byte[]) || !TYPE_NAME.equals(new String((byte[])result));
+	}
+}
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.ui/src/org/eclipse/tcf/te/tcf/filesystem/ui/nls/Messages.java b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.ui/src/org/eclipse/tcf/te/tcf/filesystem/ui/nls/Messages.java
index 9250f3c..464b24e 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.ui/src/org/eclipse/tcf/te/tcf/filesystem/ui/nls/Messages.java
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.ui/src/org/eclipse/tcf/te/tcf/filesystem/ui/nls/Messages.java
@@ -254,4 +254,5 @@
 	public static String ContentProvider_notConnected;
 	public static String UiExecutor_errorRunningOperation;
 
+	public static String FsClipboardTransfer_errorMessage;
 }
diff --git a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.ui/src/org/eclipse/tcf/te/tcf/filesystem/ui/nls/Messages.properties b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.ui/src/org/eclipse/tcf/te/tcf/filesystem/ui/nls/Messages.properties
index 0704683..b3feec3 100644
--- a/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.ui/src/org/eclipse/tcf/te/tcf/filesystem/ui/nls/Messages.properties
+++ b/target_explorer/plugins/org.eclipse.tcf.te.tcf.filesystem.ui/src/org/eclipse/tcf/te/tcf/filesystem/ui/nls/Messages.properties
@@ -175,3 +175,5 @@
 
 ContentProvider_notConnected=Please connect to see the file system on the target.
 UiExecutor_errorRunningOperation=Operation completed with errors
+
+FsClipboardTransfer_errorMessage=Received wrong transfer data