/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.jmeter.gui.tree;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import javax.swing.tree.DefaultTreeModel;
import org.apache.jmeter.config.gui.AbstractConfigGui;
import org.apache.jmeter.control.gui.TestPlanGui;
import org.apache.jmeter.control.gui.WorkBenchGui;
import org.apache.jmeter.exceptions.IllegalUserActionException;
import org.apache.jmeter.gui.GuiPackage;
import org.apache.jmeter.gui.JMeterGUIComponent;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.TestPlan;
import org.apache.jmeter.testelement.WorkBench;
import org.apache.jorphan.collections.HashTree;
import org.apache.jorphan.collections.ListedHashTree;
public class JMeterTreeModel extends DefaultTreeModel {
private static final long serialVersionUID = 240L;
public JMeterTreeModel(TestElement tp, TestElement wb) {
super(new JMeterTreeNode(wb, null));
initTree(tp,wb);
}
public JMeterTreeModel() {
this(new TestPlanGui().createTestElement(),new WorkBenchGui().createTestElement());
}
/**
* Hack to allow TreeModel to be used in non-GUI and headless mode.
*
* @deprecated - only for use by JMeter class!
* @param o - dummy
*/
@Deprecated
public JMeterTreeModel(Object o) {
this(new TestPlan(),new WorkBench());
}
/**
* Returns a list of tree nodes that hold objects of the given class type.
* If none are found, an empty list is returned.
* @param type The type of nodes, which are to be collected
* @return a list of tree nodes of the given <code>type</code>, or an empty list
*/
public List<JMeterTreeNode> getNodesOfType(Class<?> type) {
List<JMeterTreeNode> nodeList = new LinkedList<>();
traverseAndFind(type, (JMeterTreeNode) this.getRoot(), nodeList);
return nodeList;
}
/**
* Get the node for a given TestElement object.
* @param userObject The object to be found in this tree
* @return the node corresponding to the <code>userObject</code>
*/
public JMeterTreeNode getNodeOf(TestElement userObject) {
return traverseAndFind(userObject, (JMeterTreeNode) getRoot());
}
/**
* Adds the sub tree at the given node. Returns a boolean indicating whether
* the added sub tree was a full test plan.
*
* @param subTree
* The {@link HashTree} which is to be inserted into
* <code>current</code>
* @param current
* The node in which the <code>subTree</code> is to be inserted.
* Will be overridden, when an instance of {@link TestPlan} or
* {@link WorkBench} is found in the subtree.
* @return newly created sub tree now found at <code>current</code>
* @throws IllegalUserActionException
* when <code>current</code> is not an instance of
* {@link AbstractConfigGui} and no instance of {@link TestPlan}
* or {@link WorkBench} could be found in the
* <code>subTree</code>
*/
public HashTree addSubTree(HashTree subTree, JMeterTreeNode current) throws IllegalUserActionException {
for (Object o : subTree.list()) {
TestElement item = (TestElement) o;
if (item instanceof TestPlan) {
TestPlan tp = (TestPlan) item;
current = (JMeterTreeNode) ((JMeterTreeNode) getRoot()).getChildAt(0);
final TestPlan userObject = (TestPlan) current.getUserObject();
userObject.addTestElement(item);
userObject.setName(item.getName());
userObject.setFunctionalMode(tp.isFunctionalMode());
userObject.setSerialized(tp.isSerialized());
addSubTree(subTree.getTree(item), current);
} else if (item instanceof WorkBench) {
current = (JMeterTreeNode) ((JMeterTreeNode) getRoot()).getChildAt(1);
final TestElement testElement = (TestElement) current.getUserObject();
testElement.addTestElement(item);
testElement.setName(item.getName());
addSubTree(subTree.getTree(item), current);
} else {
addSubTree(subTree.getTree(item), addComponent(item, current));
}
}
return getCurrentSubTree(current);
}
/**
* Add a {@link TestElement} to a {@link JMeterTreeNode}
* @param component The {@link TestElement} to be used as data for the newly created node
* @param node The {@link JMeterTreeNode} into which the newly created node is to be inserted
* @return new {@link JMeterTreeNode} for the given <code>component</code>
* @throws IllegalUserActionException
* when the user object for the <code>node</code> is not an instance
* of {@link AbstractConfigGui}
*/
public JMeterTreeNode addComponent(TestElement component, JMeterTreeNode node) throws IllegalUserActionException {
if (node.getUserObject() instanceof AbstractConfigGui) {
throw new IllegalUserActionException("This node cannot hold sub-elements");
}
GuiPackage guiPackage = GuiPackage.getInstance();
if (guiPackage != null) {
// The node can be added in non GUI mode at startup
guiPackage.updateCurrentNode();
JMeterGUIComponent guicomp = guiPackage.getGui(component);
guicomp.clearGui();
guicomp.configure(component);
guicomp.modifyTestElement(component);
guiPackage.getCurrentGui(); // put the gui object back
// to the way it was.
}
JMeterTreeNode newNode = new JMeterTreeNode(component, this);
// This check the state of the TestElement and if returns false it
// disable the loaded node
try {
newNode.setEnabled(component.isEnabled());
} catch (Exception e) { // TODO - can this ever happen?
newNode.setEnabled(true);
}
this.insertNodeInto(newNode, node, node.getChildCount());
return newNode;
}
public void removeNodeFromParent(JMeterTreeNode node) {
if (!(node.getUserObject() instanceof TestPlan) && !(node.getUserObject() instanceof WorkBench)) {
super.removeNodeFromParent(node);
}
}
private void traverseAndFind(Class<?> type, JMeterTreeNode node, List<JMeterTreeNode> nodeList) {
if (type.isInstance(node.getUserObject())) {
nodeList.add(node);
}
Enumeration<JMeterTreeNode> enumNode = node.children();
while (enumNode.hasMoreElements()) {
JMeterTreeNode child = enumNode.nextElement();
traverseAndFind(type, child, nodeList);
}
}
private JMeterTreeNode traverseAndFind(TestElement userObject, JMeterTreeNode node) {
if (userObject == node.getUserObject()) {
return node;
}
Enumeration<JMeterTreeNode> enumNode = node.children();
while (enumNode.hasMoreElements()) {
JMeterTreeNode child = enumNode.nextElement();
JMeterTreeNode result = traverseAndFind(userObject, child);
if (result != null) {
return result;
}
}
return null;
}
/**
* Get the current sub tree for a {@link JMeterTreeNode}
* @param node The {@link JMeterTreeNode} from which the sub tree is to be taken
* @return newly copied sub tree
*/
public HashTree getCurrentSubTree(JMeterTreeNode node) {
ListedHashTree hashTree = new ListedHashTree(node);
Enumeration<JMeterTreeNode> enumNode = node.children();
while (enumNode.hasMoreElements()) {
JMeterTreeNode child = enumNode.nextElement();
hashTree.add(node, getCurrentSubTree(child));
}
return hashTree;
}
/**
* Get the {@link TestPlan} from the root of this tree
* @return The {@link TestPlan} found at the root of this tree
*/
public HashTree getTestPlan() {
return getCurrentSubTree((JMeterTreeNode) ((JMeterTreeNode) this.getRoot()).getChildAt(0));
}
/**
* Get the {@link WorkBench} from the root of this tree
* @return The {@link WorkBench} found at the root of this tree
*/
public HashTree getWorkBench() {
return getCurrentSubTree((JMeterTreeNode) ((JMeterTreeNode) this.getRoot()).getChildAt(1));
}
/**
* Clear the test plan, and use default node for test plan and workbench.
*
* N.B. Should only be called by {@link GuiPackage#clearTestPlan()}
*/
public void clearTestPlan() {
TestElement tp = new TestPlanGui().createTestElement();
clearTestPlan(tp);
}
/**
* Clear the test plan, and use specified node for test plan and default node for workbench
*
* N.B. Should only be called by {@link GuiPackage#clearTestPlan(TestElement)}
*
* @param testPlan the node to use as the testplan top node
*/
public void clearTestPlan(TestElement testPlan) {
// Remove the workbench and testplan nodes
int children = getChildCount(getRoot());
while (children > 0) {
JMeterTreeNode child = (JMeterTreeNode)getChild(getRoot(), 0);
super.removeNodeFromParent(child);
children = getChildCount(getRoot());
}
// Init the tree
initTree(testPlan,new WorkBenchGui().createTestElement()); // Assumes this is only called from GUI mode
}
/**
* Initialize the model with nodes for testplan and workbench.
*
* @param tp the element to use as testplan
* @param wb the element to use as workbench
*/
private void initTree(TestElement tp, TestElement wb) {
// Insert the test plan node
insertNodeInto(new JMeterTreeNode(tp, this), (JMeterTreeNode) getRoot(), 0);
// Insert the workbench node
insertNodeInto(new JMeterTreeNode(wb, this), (JMeterTreeNode) getRoot(), 1);
// Let others know that the tree content has changed.
// This should not be necessary, but without it, nodes are not shown when the user
// uses the Close menu item
nodeStructureChanged((JMeterTreeNode)getRoot());
}
}