/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.gui.dnd;
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.io.File;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import javax.swing.SwingUtilities;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXParseException;
import com.rapidminer.Process;
import com.rapidminer.RapidMiner;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.gui.flow.processrendering.annotations.model.WorkflowAnnotation;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.io.process.XMLImporter;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorCreationException;
import com.rapidminer.operator.UnknownParameterInformation;
import com.rapidminer.operator.internal.ProcessEmbeddingOperator;
import com.rapidminer.operator.io.RepositorySource;
import com.rapidminer.operator.nio.file.LoadFileOperator;
import com.rapidminer.repository.BlobEntry;
import com.rapidminer.repository.DataEntry;
import com.rapidminer.repository.Entry;
import com.rapidminer.repository.ProcessEntry;
import com.rapidminer.repository.RepositoryLocation;
import com.rapidminer.studio.io.gui.internal.DataImportWizardBuilder;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.LogService;
import com.rapidminer.tools.OperatorService;
import com.rapidminer.tools.Tools;
/**
* Transfer handler that supports dragging and dropping operators and workflow annotations.
*
* @author Simon Fischer, Marius Helf, Michael Knopf, Marco Boeck
*
*/
public abstract class ReceivingOperatorTransferHandler extends OperatorTransferHandler {
private static final long serialVersionUID = 5355397064093668659L;
private final List<DataFlavor> acceptableFlavors = new LinkedList<>();
public ReceivingOperatorTransferHandler() {
acceptableFlavors.add(TransferableOperator.LOCAL_TRANSFERRED_OPERATORS_FLAVOR);
acceptableFlavors.add(TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_FLAVOR);
acceptableFlavors.add(TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_LIST_FLAVOR);
acceptableFlavors.add(TransferableAnnotation.LOCAL_OPERATOR_ANNOTATION_FLAVOR);
acceptableFlavors.add(TransferableAnnotation.LOCAL_PROCESS_ANNOTATION_FLAVOR);
acceptableFlavors.add(DataFlavor.javaFileListFlavor);
acceptableFlavors.add(DataFlavor.stringFlavor);
}
/**
* Drops the operator at the given location. The location may be null, indicating that this is a
* paste.
*/
protected abstract boolean dropNow(List<Operator> newOperators, Point loc);
protected abstract boolean dropNow(WorkflowAnnotation anno, Point loc);
protected abstract boolean dropNow(String processXML);
protected abstract void markDropOver(Point dropPoint);
protected abstract boolean isDropLocationOk(List<Operator> operator, Point loc);
protected abstract void dropEnds();
protected abstract Process getProcess();
// Drop Support
@Override
public boolean canImport(TransferSupport ts) {
for (DataFlavor flavor : acceptableFlavors) {
if (ts.isDataFlavorSupported(flavor)) {
if (ts.isDrop()) {
markDropOver(ts.getDropLocation().getDropPoint());
}
return true;
}
}
return false;
}
@Override
public boolean importData(TransferSupport ts) {
if (!canImport(ts)) {
return false;
}
DataFlavor acceptedFlavor = null;
for (DataFlavor flavor : acceptableFlavors) {
if (ts.isDataFlavorSupported(flavor)) {
acceptedFlavor = flavor;
break;
}
}
if (acceptedFlavor == null) {
dropEnds();
return false; // cannot happen
}
Object transferData;
try {
transferData = ts.getTransferable().getTransferData(acceptedFlavor);
} catch (Exception e1) {
LogService.getRoot()
.log(Level.WARNING,
I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.dnd.ReceivingOperatorTransferHandler.error_while_accepting_drop"),
e1);
dropEnds();
return false;
}
List<Operator> newOperators;
if (acceptedFlavor.equals(DataFlavor.javaFileListFlavor)) {
@SuppressWarnings("unchecked")
final File file = ((List<File>) transferData).get(0);
if (file.getName().toLowerCase().endsWith("." + RapidMiner.PROCESS_FILE_EXTENSION)) {
// This is a process file
try {
Operator processEmbedder = OperatorService.createOperator(ProcessEmbeddingOperator.OPERATOR_KEY);
processEmbedder.setParameter(ProcessEmbeddingOperator.PARAMETER_PROCESS_FILE, file.getAbsolutePath());
newOperators = Collections.<Operator> singletonList(processEmbedder);
} catch (Exception e) {
SwingTools.showSimpleErrorMessage("cannot_create_process_embedder", e);
dropEnds();
return false;
}
} else {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
DataImportWizardBuilder importWizardBuilder = new DataImportWizardBuilder();
importWizardBuilder.forFile(file.toPath()).build(RapidMinerGUI.getMainFrame()).getDialog()
.setVisible(true);
}
});
dropEnds();
return true;
}
} else if (acceptedFlavor.equals(TransferableOperator.LOCAL_TRANSFERRED_OPERATORS_FLAVOR)) {
// This is an operator
if (transferData instanceof Operator[]) {
newOperators = Arrays.asList((Operator[]) transferData);
} else {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.gui.dnd.ReceivingOperatorTransferHandler.expected_operator", acceptedFlavor);
dropEnds();
return false;
}
} else if (acceptedFlavor.equals(DataFlavor.stringFlavor)) {
if (transferData instanceof String) {
try {
Process process = new Process(((String) transferData).trim());
newOperators = process.getRootOperator().getSubprocess(0).getOperators();
} catch (Exception e) {
try {
Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder()
.parse(new InputSource(new StringReader((String) transferData)));
NodeList opElements = document.getDocumentElement().getChildNodes();
Operator newOp = null;
for (int i = 0; i < opElements.getLength(); i++) {
Node child = opElements.item(i);
if (child instanceof Element) {
Element elem = (Element) child;
if ("operator".equals(elem.getTagName())) {
newOp = new XMLImporter(null).parseOperator(elem, RapidMiner.getVersion(), getProcess(),
new LinkedList<UnknownParameterInformation>());
break;
}
}
}
if (newOp == null) {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.gui.dnd.ReceivingOperatorTransferHandler.parsing_operator_from_clipboard_error",
transferData);
dropEnds();
return false;
}
newOperators = Collections.singletonList(newOp);
} catch (SAXParseException e1) {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.gui.processeditor.XMLEditor.failed_to_parse_process");
dropEnds();
return false;
} catch (Exception e1) {
LogService.getRoot().log(Level.WARNING,
I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.dnd.ReceivingOperatorTransferHandler.parsing_operator_from_clipboard_error_exception",
e1, transferData),
e1);
dropEnds();
return false;
}
}
} else {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.gui.dnd.ReceivingOperatorTransferHandler.expected_string", acceptedFlavor);
dropEnds();
return false;
}
} else if (acceptedFlavor.equals(TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_FLAVOR)) {
if (transferData instanceof RepositoryLocation) {
RepositoryLocation repositoryLocation = (RepositoryLocation) transferData;
newOperators = Collections.singletonList(createOperator(repositoryLocation));
if (newOperators == null) {
return false;
}
} else {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.gui.dnd.ReceivingOperatorTransferHandler.expected_repositorylocation",
acceptedFlavor);
dropEnds();
return false;
}
} else if (acceptedFlavor.equals(TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_LIST_FLAVOR)) {
if (transferData instanceof RepositoryLocationList) {
RepositoryLocationList repositoryLocationList = (RepositoryLocationList) transferData;
newOperators = new LinkedList<>();
for (RepositoryLocation loc : repositoryLocationList.getAll()) {
List<Operator> list = Collections.singletonList(createOperator(loc));
if (list == null) {
return false;
} else {
newOperators.addAll(list);
}
}
} else {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.gui.dnd.ReceivingOperatorTransferHandler.expected_repositorylocationlist",
acceptedFlavor);
dropEnds();
return false;
}
} else if (acceptedFlavor.equals(TransferableAnnotation.LOCAL_OPERATOR_ANNOTATION_FLAVOR)
|| acceptedFlavor.equals(TransferableAnnotation.LOCAL_PROCESS_ANNOTATION_FLAVOR)) {
newOperators = new LinkedList<>();
} else {
// cannot happen
dropEnds();
return false;
}
if (ts.isDrop()) {
// drop
Point loc = ts.getDropLocation().getDropPoint();
boolean dropLocationOk = !ts.isDrop() || isDropLocationOk(newOperators, loc);
if (!dropLocationOk) {
dropEnds();
return false;
} else {
if (ts.getDropAction() == MOVE) {
for (Operator operator : newOperators) {
operator.removeAndKeepConnections(newOperators);
}
}
newOperators = Tools.cloneOperators(newOperators);
boolean result;
try {
result = dropNow(newOperators, ts.isDrop() ? loc : null);
} catch (RuntimeException e) {
LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.dnd.ReceivingOperatorTransferHandler.error_in_drop", e), e);
SwingTools.showVerySimpleErrorMessage("error_in_paste", e.getMessage(), e.getMessage());
dropEnds();
return false;
}
dropEnds();
return result;
}
} else {
// paste
if (acceptedFlavor.equals(DataFlavor.stringFlavor)) {
// handle XML String pasting differently
boolean result;
try {
result = dropNow(String.valueOf(transferData));
} catch (RuntimeException e) {
LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.dnd.ReceivingOperatorTransferHandler.error_in_paste", e), e);
SwingTools.showVerySimpleErrorMessage("error_in_paste", e.getMessage(), e.getMessage());
dropEnds();
return false;
}
dropEnds();
return result;
} else if (acceptedFlavor.equals(TransferableAnnotation.LOCAL_PROCESS_ANNOTATION_FLAVOR)
|| acceptedFlavor.equals(TransferableAnnotation.LOCAL_OPERATOR_ANNOTATION_FLAVOR)) {
boolean result;
try {
result = dropNow((WorkflowAnnotation) transferData, null);
} catch (RuntimeException e) {
LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.dnd.ReceivingOperatorTransferHandler.error_in_paste", e), e);
SwingTools.showVerySimpleErrorMessage("error_in_paste", e.getMessage(), e.getMessage());
dropEnds();
return false;
}
dropEnds();
return result;
} else {
// paste an existing Operator
newOperators = Tools.cloneOperators(newOperators);
boolean result;
try {
result = dropNow(newOperators, null);
} catch (RuntimeException e) {
LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.dnd.ReceivingOperatorTransferHandler.error_in_paste", e), e);
SwingTools.showVerySimpleErrorMessage("error_in_paste", e.getMessage(), e.getMessage());
dropEnds();
return false;
}
dropEnds();
return result;
}
}
}
/**
* Creates the operator to import from the given repository location.
*
* @param repositoryLocation
* the location which should be imported
* @return the operator or {@code null}
*/
private Operator createOperator(RepositoryLocation repositoryLocation) {
Entry entry;
try {
entry = repositoryLocation.locateEntry();
} catch (Exception e) {
// no valid entry
return null;
}
String resolvedLocation;
if (getProcess().getRepositoryLocation() != null) {
resolvedLocation = repositoryLocation.makeRelative(getProcess().getRepositoryLocation().parent());
} else {
resolvedLocation = repositoryLocation.getAbsoluteLocation();
}
if (!(entry instanceof DataEntry)) {
// can't handle non-data entries (like folders)
return null;
} else if (entry instanceof BlobEntry) {
// create Retrieve Blob operator
try {
LoadFileOperator source = OperatorService.createOperator(LoadFileOperator.class);
source.setParameter(LoadFileOperator.PARAMETER_REPOSITORY_LOCATION, resolvedLocation);
source.setParameter(LoadFileOperator.PARAMETER_SOURCE_TYPE,
String.valueOf(LoadFileOperator.SOURCE_TYPE_REPOSITORY));
return source;
} catch (OperatorCreationException e1) {
LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.dnd.ReceivingOperatorTransferHandler.creating_repositorysource_error", e1), e1);
return null;
}
} else if (entry instanceof ProcessEntry) {
// create Execute Process operator
try {
Operator embedder = OperatorService.createOperator(ProcessEmbeddingOperator.OPERATOR_KEY);
embedder.setParameter(ProcessEmbeddingOperator.PARAMETER_PROCESS_FILE, resolvedLocation);
embedder.rename("Execute " + repositoryLocation.getName());
return embedder;
} catch (OperatorCreationException e1) {
LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.dnd.ReceivingOperatorTransferHandler.creating_repositorysource_error", e1), e1);
return null;
}
} else {
// create Retrieve operator
try {
RepositorySource source = OperatorService.createOperator(RepositorySource.class);
source.setParameter(RepositorySource.PARAMETER_REPOSITORY_ENTRY, resolvedLocation);
source.rename("Retrieve " + repositoryLocation.getName());
return source;
} catch (OperatorCreationException e1) {
LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.gui.dnd.ReceivingOperatorTransferHandler.creating_repositorysource_error", e1), e1);
return null;
}
}
}
}