/******************************************************************************* * Copyright (c) 2004, 2010 BREDEX GmbH. * 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: * BREDEX GmbH - initial API and implementation and/or initial documentation *******************************************************************************/ package org.eclipse.jubula.client.ui.rcp.controllers.dnd; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.collections.map.HashedMap; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerDropAdapter; import org.eclipse.jubula.client.core.businessprocess.CapBP; import org.eclipse.jubula.client.core.businessprocess.ParamNameBP; import org.eclipse.jubula.client.core.businessprocess.ParamNameBPDecorator; import org.eclipse.jubula.client.core.businessprocess.db.TestCaseBP; import org.eclipse.jubula.client.core.events.DataEventDispatcher; import org.eclipse.jubula.client.core.events.DataEventDispatcher.DataState; import org.eclipse.jubula.client.core.events.DataEventDispatcher.UpdateState; import org.eclipse.jubula.client.core.model.IAbstractContainerPO; import org.eclipse.jubula.client.core.model.ICapPO; import org.eclipse.jubula.client.core.model.ICommentPO; import org.eclipse.jubula.client.core.model.IControllerPO; import org.eclipse.jubula.client.core.model.IEventExecTestCasePO; import org.eclipse.jubula.client.core.model.IExecTestCasePO; import org.eclipse.jubula.client.core.model.INodePO; import org.eclipse.jubula.client.core.model.IParamDescriptionPO; import org.eclipse.jubula.client.core.model.IParamNodePO; import org.eclipse.jubula.client.core.model.ISpecTestCasePO; import org.eclipse.jubula.client.core.model.ITestSuitePO; import org.eclipse.jubula.client.core.model.NodeMaker; import org.eclipse.jubula.client.core.model.TDCell; import org.eclipse.jubula.client.core.persistence.EditSupport; import org.eclipse.jubula.client.core.persistence.PMAlreadyLockedException; import org.eclipse.jubula.client.core.persistence.PMDirtyVersionException; import org.eclipse.jubula.client.core.persistence.PMException; import org.eclipse.jubula.client.core.utils.ModelParamValueConverter; import org.eclipse.jubula.client.core.utils.RefToken; import org.eclipse.jubula.client.ui.rcp.controllers.MultipleTCBTracker; import org.eclipse.jubula.client.ui.rcp.controllers.PMExceptionHandler; import org.eclipse.jubula.client.ui.rcp.editors.AbstractTestCaseEditor; import org.eclipse.jubula.client.ui.rcp.editors.JBEditorHelper; import org.eclipse.jubula.client.ui.rcp.editors.NodeEditorInput; import org.eclipse.jubula.client.ui.rcp.i18n.Messages; import org.eclipse.jubula.client.ui.rcp.utils.NodeTargetCalculator; import org.eclipse.jubula.client.ui.rcp.utils.NodeTargetCalculator.NodeTarget; import org.eclipse.jubula.client.ui.rcp.views.TestCaseBrowser; import org.eclipse.jubula.client.ui.utils.ErrorHandlingUtil; import org.eclipse.jubula.tools.internal.exception.InvalidDataException; import org.eclipse.jubula.tools.internal.i18n.I18n; import org.eclipse.jubula.tools.internal.messagehandling.MessageIDs; import org.eclipse.osgi.util.NLS; /** * Utility class containing methods for use in drag and drop as well as * cut and paste operations in the Test Case Editor. * * @author BREDEX GmbH * @created 25.03.2008 */ public class TCEditorDndSupport extends AbstractEditorDndSupport { /** * Private constructor */ private TCEditorDndSupport() { // Do nothing } /** * * @param targetEditor The editor to which the item is to be dropped/pasted. * @param toDrop The items that were dragged/cut. * @param dropTarget The drop/paste target. * @param dropPosition One of the values defined in ViewerDropAdapter to * indicate the drop position relative to the drop * target. * @return <code>true</code> if the drop/paste was successful. * Otherwise <code>false</code>. */ public static boolean performDrop(AbstractTestCaseEditor targetEditor, IStructuredSelection toDrop, INodePO dropTarget, int dropPosition) { if (targetEditor.getEditorHelper().requestEditableState() != JBEditorHelper.EditableState.OK) { return false; } @SuppressWarnings("unchecked") List<Object> selectedElements = toDrop.toList(); Collections.reverse(selectedElements); Iterator iter = selectedElements.iterator(); while (iter.hasNext()) { INodePO droppedNode = null; Object obj = iter.next(); if (!(obj instanceof INodePO)) { return false; } INodePO node = (INodePO)obj; boolean exp = targetEditor.getTreeViewer(). getExpandedState(dropTarget); if (!(node instanceof ISpecTestCasePO)) { NodeTarget tar = NodeTargetCalculator.calcNodeTarget(node, dropTarget, dropPosition, exp); if (tar != null) { droppedNode = moveNode(node, tar.getNode(), tar.getPos()); } } else { droppedNode = performDrop(targetEditor, dropTarget, dropPosition, (ISpecTestCasePO)node, exp); if (droppedNode == null) { return false; } } postDropAction(droppedNode, targetEditor); } targetEditor.runLocalChecks(); return true; } /** * * @param targetEditor The editor to which the item is to be pasted. * @param toDrop The items that were copy. * @param dropTarget The paste target. * indicate the drop position relative to the drop * target. * @return <code>true</code> if the paste was successful. * Otherwise <code>false</code>. */ public static boolean copyPaste(AbstractTestCaseEditor targetEditor, IStructuredSelection toDrop, INodePO dropTarget) { if (targetEditor.getEditorHelper().requestEditableState() != JBEditorHelper.EditableState.OK || !(dropTarget.getSpecAncestor() instanceof ISpecTestCasePO)) { return false; } if (toDrop.isEmpty()) { return false; } ISpecTestCasePO targetSpecTC = (ISpecTestCasePO) dropTarget.getSpecAncestor(); boolean noTCHandlers = false; INodePO last = null; for (Object obj : getFullList(toDrop)) { if (!(obj instanceof INodePO)) { return false; } if (obj instanceof IEventExecTestCasePO) { last = copyPasteEventExecTestCase(targetEditor, (IEventExecTestCasePO)obj, targetSpecTC); continue; } noTCHandlers = true; if (obj instanceof IParamNodePO) { IParamNodePO paramNode = (IParamNodePO)obj; if (!(targetSpecTC.equals(paramNode.getSpecAncestor()) || checkParentParameters(targetSpecTC, paramNode, null, false))) { return false; } } } if (noTCHandlers) { NodeTarget tar = NodeTargetCalculator.calcNodeTarget( (INodePO) toDrop.getFirstElement(), dropTarget, ViewerDropAdapter.LOCATION_ON, false); last = copyPasteNodes(targetEditor, tar.getNode(), toDrop.toList(), tar.getPos()); } postDropAction(last, targetEditor); targetEditor.runLocalChecks(); return true; } /** * Recursively copies a list of nodes * @param editor the editor * @param target the target node * @param nodes the nodes to copy * @param fromPos the position * @return the last node */ private static INodePO copyPasteNodes(AbstractTestCaseEditor editor, INodePO target, List<INodePO> nodes, int fromPos) { ParamNameBPDecorator pMapper = editor.getEditorHelper() .getEditSupport().getParamMapper(); int pos = fromPos; INodePO last = null; for (INodePO node : nodes) { if (node instanceof ICapPO) { last = copyPasteCap(editor, (ICapPO) node, target, pos); } else if (node instanceof IExecTestCasePO) { last = copyPasteExecTestCase(editor, (IExecTestCasePO) node, target, pos); } else if (node instanceof ICommentPO) { INodePO comm = NodeMaker.createCommentPO( ((ICommentPO) node).getName()); fillNode(node, comm); target.addNode(pos, comm); last = comm; } else if (node instanceof IControllerPO) { // we assume a very strict structure here // controllers have Container children which in turn can only have // CapPO, ExecTCPO, CommentPO, ... children (so no Controllers or Containers) IControllerPO controller = NodeMaker. createControllerPO((IControllerPO) node); List<INodePO> nodeList = node.getUnmodifiableNodeList(); List<INodePO> contList = controller.getUnmodifiableNodeList(); if (node instanceof IParamNodePO) { fillParamNode((IParamNodePO) node, (IParamNodePO) controller); checkParentParameters((ISpecTestCasePO) target. getSpecAncestor(), (IParamNodePO) controller, pMapper, true); } else { fillNode(node, controller); } target.addNode(pos, controller); for (int i = 0; i < nodeList.size(); i++) { copyPasteNodes(editor, contList.get(i), nodeList.get(i).getUnmodifiableNodeList(), 0); } last = controller; } pos++; } return last; } /** * * @param targetEditor The editor to which the item is to be dropped/pasted. * @param origEvent The item that was dragged/cut. * @param targetNode target parent node * @return the node */ public static INodePO copyPasteEventExecTestCase( AbstractTestCaseEditor targetEditor, IEventExecTestCasePO origEvent, ISpecTestCasePO targetNode) { IEventExecTestCasePO newEvent = null; if (targetNode.getEventExecTcMap() .containsKey(origEvent.getEventType())) { boolean status = MessageDialog.openQuestion(null, Messages.DoubleEventTypeTitle, NLS.bind(Messages .TestCaseEditorDoubleEventTypeErrorDetailOverwrite, new Object[]{targetNode.getName(), I18n.getString(origEvent.getEventType())})); if (status) { targetNode.getEventExecTcMap() .remove(origEvent.getEventType()); } else { return null; } } final EditSupport editSupport = targetEditor.getEditorHelper() .getEditSupport(); ParamNameBPDecorator pMapper = targetEditor.getEditorHelper() .getEditSupport().getParamMapper(); if (targetNode.equals(origEvent.getParentNode()) || checkParentParameters(targetNode, origEvent, pMapper, false)) { try { newEvent = NodeMaker .createEventExecTestCasePO(origEvent .getSpecTestCase(), targetNode); fillExec(origEvent, newEvent, false); checkParentParameters(targetNode, newEvent, pMapper, true); TestCaseBP.addEventHandler(editSupport, targetNode, newEvent); targetEditor.getEditorHelper().setDirty(true); DataEventDispatcher.getInstance() .fireDataChangedListener(newEvent, DataState.Added, UpdateState.onlyInEditor); } catch (InvalidDataException e) { // no log entry, because it is a use case! ErrorHandlingUtil.createMessageDialog( MessageIDs.E_DOUBLE_EVENT, null, new String[]{NLS.bind( Messages.TestCaseEditorDoubleEventTypeErrorDetail, new Object[]{targetNode.getName(), I18n.getString(origEvent.getEventType())})}); } catch (PMException e) { PMExceptionHandler.handlePMExceptionForMasterSession(e); } } else { return null; } return newEvent; } /** * * @param targetEditor The editor to which the item is to be dropped/pasted. * @param execTestCase The item that was dragged/cut. * @param dropPosition One of the values defined in ViewerDropAdapter to * indicate the drop position relative to the drop * target. * @param targetNode target parent node * @return the new node */ public static INodePO copyPasteExecTestCase( AbstractTestCaseEditor targetEditor, IExecTestCasePO execTestCase, INodePO targetNode, int dropPosition) { IExecTestCasePO newExecTestCase = NodeMaker .createExecTestCasePO(execTestCase.getSpecTestCase()); fillExec(execTestCase, newExecTestCase, false); ParamNameBPDecorator pMapper = targetEditor.getEditorHelper() .getEditSupport().getParamMapper(); checkParentParameters((ISpecTestCasePO) targetNode.getSpecAncestor(), newExecTestCase, pMapper, true); TestCaseBP.addReferencedTestCase(targetNode, newExecTestCase, dropPosition); targetEditor.getEditorHelper().setDirty(true); DataEventDispatcher.getInstance() .fireDataChangedListener(newExecTestCase, DataState.Added, UpdateState.onlyInEditor); return newExecTestCase; } /** * * @param targetEditor The editor to which the item is to be dropped/pasted. * @param cap The item that was dragged/cut. * @param targetNode target parent node * @param dropPosition One of the values defined in ViewerDropAdapter to * indicate the drop position relative to the drop * target. * @return the new node */ public static INodePO copyPasteCap( AbstractTestCaseEditor targetEditor, ICapPO cap, INodePO targetNode, int dropPosition) { ParamNameBPDecorator pMapper = targetEditor.getEditorHelper() .getEditSupport().getParamMapper(); ICapPO newCap = CapBP.createCapWithDefaultParams(cap.getName(), cap.getComponentName(), cap.getComponentType(), cap.getActionName()); fillCap(cap, newCap); newCap.setParentNode(targetNode); targetNode.addNode(dropPosition, newCap); checkParentParameters((ISpecTestCasePO) targetNode.getSpecAncestor(), newCap, pMapper, true); targetEditor.getTreeViewer().expandToLevel(targetNode, 1); DataEventDispatcher.getInstance().fireParamChangedListener(); return newCap; } /** * * @param targetEditor The editor to which the item is to be dropped/pasted. * @param toDrop The item that was dragged/cut. * @param dropTarget The drop/paste target. * @param dropPosition One of the values defined in ViewerDropAdapter to * indicate the drop position relative to the drop * target. * @param exp whether target is expanded * @return the new IExecTestCasePO object if the drop/paste was successful. * Otherwise <code>null</code>. */ private static IExecTestCasePO performDrop( AbstractTestCaseEditor targetEditor, INodePO dropTarget, int dropPosition, ISpecTestCasePO toDrop, boolean exp) { INodePO target = dropTarget; if (target != toDrop) { EditSupport editSupport = targetEditor.getEditorHelper().getEditSupport(); try { NodeTarget tar = NodeTargetCalculator.calcNodeTarget(toDrop, dropTarget, dropPosition, exp); if (tar == null) { return null; } if (target instanceof ISpecTestCasePO) { return dropOnSpecTc(editSupport, toDrop, tar.getNode(), tar.getPos()); } else if (target instanceof ITestSuitePO) { return dropOnTestsuite(editSupport, (ITestSuitePO)target, toDrop, tar.getPos()); } else { return dropOnSgElse(editSupport, toDrop, tar.getNode(), tar.getPos()); } } catch (PMException e) { NodeEditorInput inp = (NodeEditorInput)targetEditor. getAdapter(NodeEditorInput.class); INodePO inpNode = inp.getNode(); PMExceptionHandler.handlePMExceptionForMasterSession(e); // If an object was already locked, *and* the locked // object is not the editor Test Case, *and* the editor // is dirty, then we do *not* want to revert all // editor changes. // The additional test as to whether the the editor is // marked as dirty is important because, due to the // requestEditableState() call earlier in this method, // the editor TC is locked (even though the editor // isn't dirty). Reopening the editor removes this lock. if (!(e instanceof PMAlreadyLockedException && ((PMAlreadyLockedException)e) .getLockedObject() != null && !((PMAlreadyLockedException)e) .getLockedObject().equals(inpNode)) || !targetEditor.isDirty()) { try { targetEditor.reOpenEditor(inpNode); } catch (PMException e1) { PMExceptionHandler.handlePMExceptionForEditor(e, targetEditor); } } } } return null; } /** * Check the parameter of the new parent node. If possible it generate * the needed parameters and return a modified execTestCase * else it drop up an error message and return null. * * @param targetNode target node * @param paramNode original exec test case node * @param pMapper ParamNameBPDecorator * @param create if <code>true</code> the parameter will be created * @return <code>false</code> if target parent node has a different parameter type * with same name than the new parameter node .Otherwise <code>true</code>. */ private static boolean checkParentParameters( ISpecTestCasePO targetNode, IParamNodePO paramNode, ParamNameBPDecorator pMapper, boolean create) { for (Iterator<TDCell> it = paramNode .getParamReferencesIterator(); it.hasNext();) { TDCell cell = it.next(); String guid = paramNode.getDataManager() .getUniqueIds().get(cell.getCol()); IParamDescriptionPO childDesc = paramNode .getParameterForUniqueId(guid); // The childDesc can be null if the parameter has been // removed in another session and not yet updated in the // current editor session. if (childDesc != null) { ModelParamValueConverter conv = new ModelParamValueConverter(cell.getTestData(), paramNode, childDesc); List<RefToken> refTokens = conv.getRefTokens(); for (RefToken refToken : refTokens) { String oldGUID = RefToken.extractCore(refToken .getModelString()); String paramName = ParamNameBP.getInstance().getName( oldGUID, childDesc.getParentProjectId()); @SuppressWarnings("unchecked") Map<String, String> oldToNewGuids = new HashedMap(); IParamDescriptionPO parentParamDescr = targetNode .getParameterForName(paramName); if (parentParamDescr == null) { if (create) { targetNode.addParameter(childDesc.getType(), paramName, pMapper); parentParamDescr = targetNode .getParameterForName(paramName); } } else if (!parentParamDescr.getType() .equals(childDesc.getType())) { MessageDialog.openInformation(null, Messages.ParameterConfligtDetectedTitle, NLS.bind(Messages.ParameterConfligtDetected, new Object[] {parentParamDescr.getName(), targetNode.getName()})); return false; } if (create) { if (parentParamDescr != null) { String newGuid = parentParamDescr.getUniqueId(); oldToNewGuids.put(oldGUID, newGuid); } // update test data of child with UUID for reference conv.replaceUuidsInReferences(oldToNewGuids); cell.setTestData(conv.getModelString()); } } } } return true; } /** * * @param toDrop The items that were copy. * @param dropTarget The paste target. * @return <code>true</code> if the given information indicates that the * paste is valid. Otherwise <code>false</code>. */ public static boolean validateCopy(IStructuredSelection toDrop, INodePO dropTarget) { if (toDrop == null || toDrop.isEmpty() || dropTarget == null || !(dropTarget.getSpecAncestor() instanceof ISpecTestCasePO)) { return false; } ISpecTestCasePO targSpecTC; targSpecTC = (ISpecTestCasePO)dropTarget.getSpecAncestor(); INodePO par = null; for (Object obj : toDrop.toArray()) { if (!(obj instanceof INodePO)) { return false; } if (obj instanceof IControllerPO && !(dropTarget instanceof ISpecTestCasePO) && dropTarget.getParentNode() != targSpecTC) { // Controllers can only be direct children of SpecTCs return false; } INodePO dropNode = (INodePO)obj; if (par != null && !par.equals(dropNode.getParentNode())) { return false; } } for (Object obj : getFullList(toDrop)) { INodePO dropNode = (INodePO)obj; par = dropNode.getParentNode(); if (!(dropNode instanceof IExecTestCasePO || dropNode instanceof ISpecTestCasePO) || targSpecTC.equals(dropNode.getSpecAncestor())) { continue; } ISpecTestCasePO dropSpecTC = null; if (dropNode instanceof ISpecTestCasePO) { dropSpecTC = (ISpecTestCasePO)dropNode; } else if (dropNode instanceof IExecTestCasePO) { dropSpecTC = ((IExecTestCasePO)dropNode).getSpecTestCase(); } if (dropSpecTC.hasCircularDependences(targSpecTC)) { return false; } } return true; } /** * Returns the full list of nodes by adding grandchildren of ControllerPOs * @param nodes the list of nodes * @return the new list */ private static List getFullList(IStructuredSelection nodes) { List<Object> res = new ArrayList<>(nodes.size()); for (Iterator it = nodes.iterator(); it.hasNext(); ) { Object next = it.next(); res.add(next); if (next instanceof IControllerPO) { for (Iterator<INodePO> itC = ((INodePO) next).getAllNodeIter(); itC.hasNext(); ) { res.add(itC.next()); } } } return res; } /** * * @param sourceViewer The viewer containing the dragged/cut item. * @param targetViewer The viewer to which the item is to be dropped/pasted. * @param toDrop The items that were dragged/cut. * @param dropTarget The drop/paste target. * @param allowFromBrowser Whether items from the Test Case Browser are * allowed to be dropped/pasted. * @return <code>true</code> if the given information indicates that the * drop/paste is valid. Otherwise <code>false</code>. */ public static boolean validateDrop(Viewer sourceViewer, Viewer targetViewer, IStructuredSelection toDrop, INodePO dropTarget, boolean allowFromBrowser) { if (toDrop == null || toDrop.isEmpty() || dropTarget == null) { return false; } if (sourceViewer != null && !sourceViewer.equals(targetViewer)) { boolean foundOne = false; for (TestCaseBrowser tcb : MultipleTCBTracker.getInstance() .getOpenTCBs()) { if (sourceViewer.equals(tcb.getTreeViewer())) { foundOne = true; } } if (!(allowFromBrowser && foundOne)) { return false; } } Iterator iter = toDrop.iterator(); while (iter.hasNext()) { Object obj = iter.next(); if (!(obj instanceof INodePO) || obj instanceof IAbstractContainerPO) { return false; } if (obj instanceof IControllerPO && !(dropTarget instanceof ISpecTestCasePO) && !(dropTarget.getParentNode() instanceof ISpecTestCasePO)) { return false; } INodePO transferGUI = (INodePO)obj; if (!(transferGUI instanceof ISpecTestCasePO) && transferGUI.getSpecAncestor() != dropTarget.getSpecAncestor()) { return false; } if (!(transferGUI instanceof ISpecTestCasePO)) { continue; } ISpecTestCasePO specTcGUI = (ISpecTestCasePO) dropTarget. getSpecAncestor(); if (transferGUI.hasCircularDependences(specTcGUI)) { return false; } } return true; } /** * @param editSupport The EditSupport in which to perform the action. * @param node the node to be dropped. * @param target the target node. * @param pos the position * @throws PMDirtyVersionException in case of version conflict (dirty read) * @throws PMAlreadyLockedException if the origSpecTc is already locked by another user * @throws PMException in case of unspecified db error * * @return the new execTestCaseNode */ private static IExecTestCasePO dropOnSpecTc(EditSupport editSupport, INodePO node, INodePO target, int pos) throws PMAlreadyLockedException, PMDirtyVersionException, PMException { return TestCaseBP.addReferencedTestCase(editSupport, target, (ISpecTestCasePO)node, pos); } /** * Drops the given TestCase on the given TestSuite. * The TestCase will be inserted at the end. * @param editSupport The EditSupport in which to perform the action. * @param testSuite the TestSuite to drop on * @param testcase the TestCAse to drop * @param pos the position * @throws PMAlreadyLockedException in case of persistence error * @throws PMDirtyVersionException in case of persistence error * @throws PMException in case of persistence error * * @return the new execTestCaseNode */ private static IExecTestCasePO dropOnTestsuite(EditSupport editSupport, ITestSuitePO testSuite, ISpecTestCasePO testcase, int pos) throws PMAlreadyLockedException, PMDirtyVersionException, PMException { return TestCaseBP.addReferencedTestCase(editSupport, testSuite, testcase, pos); } /** * @param editSupport The EditSupport in which to perform the action. * @param node the node to be dropped * @param target the target node. * @param pos the position * @throws PMDirtyVersionException in case of version conflict (dirty read) * @throws PMAlreadyLockedException if the origSpecTc is already locked by another user * @throws PMException in case of unspecified db error * * @return the new execTestCaseNode */ private static IExecTestCasePO dropOnSgElse(EditSupport editSupport, INodePO node, INodePO target, int pos) throws PMAlreadyLockedException, PMDirtyVersionException, PMException { return TestCaseBP.addReferencedTestCase(editSupport, target, (ISpecTestCasePO) node, pos); } }