JTree how to set a leaf node as expandable - java

I'm a little lost as what to search for here as I can't seem to find anything.
Basically I'm using a JTree to display a server and the structures that live within it. I don't want to load the whole structure as it takes a long time to query, so instead I'd like to load it in as the user expands nodes.
The structure looks something like this (where I always know the root server node)
Server
-Tables
-Columns
I'd like to first add 'Server', then when the user expands it I'd like to add 'Tables', then when the user expands that I'd like to add 'Columns'.
I've follows a tutorial on setting up the whole TreeWillExpandListener, here is my code:
public class ServerViewer extends JScrollPane
implements TreeModelListener, TreeExpansionListener, TreeWillExpandListener {
/**
*
*/
private static final long serialVersionUID = 1L;
public ServerViewer(
final Model model) {
Validate.isTrue(model != null, "'model' must not be null.");
this.model = model;
// Create root node of tree
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Servers (2)");
createNodes(rootNode);
// Create tree model
DefaultTreeModel treeModel = new DefaultTreeModel(rootNode);
treeModel.addTreeModelListener(this);
// Create tree
this.browserTree = new JTree(treeModel);
//this.browserTree.setRootVisible(false);
this.browserTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
this.browserTree.addTreeExpansionListener(this);
this.browserTree.addTreeWillExpandListener(this);
// Add components
this.getViewport().add(this.browserTree);
}
/**
* Populates the root node with the expected default nodes.
*
* #param rootNode The node to populate. Must not be null.
*/
private void createNodes(DefaultMutableTreeNode rootNode) {
Validate.isTrue(rootNode != null, "'rootNode' must not be null.");
for (ServerModel server: this.model.getServers()) {
DefaultMutableTreeNode serverNode = new DefaultMutableTreeNode(server, true);
rootNode.add(serverNode);
}
}
/**
* #return The tree which this uses as a browser. Will not be null.
*/
public JTree getBrowserTree() {
return this.browserTree;
}
#Override
public void treeNodesChanged(
TreeModelEvent arg0) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)arg0.getTreePath().getLastPathComponent();
/*
* If the event lists children, then the changed
* node is the child of the node we have already
* gotten. Otherwise, the changed node and the
* specified node are the same.
*/
try {
int index = arg0.getChildIndices()[0];
node = (DefaultMutableTreeNode)node.getChildAt(index);
}
catch (NullPointerException exc) {}
System.out.println("The user has finished editing the node.");
System.out.println("New value: " + node.getUserObject());
}
#Override
public void treeNodesInserted(
TreeModelEvent arg0) {
}
#Override
public void treeNodesRemoved(
TreeModelEvent arg0) {
}
#Override
public void treeStructureChanged(
TreeModelEvent arg0) {
}
#Override
public void treeCollapsed(TreeExpansionEvent arg0) {
}
#Override
public void treeExpanded(TreeExpansionEvent arg0) {
}
#Override
public void treeWillCollapse(
TreeExpansionEvent arg0) throws ExpandVetoException {
TreePath path = arg0.getPath();
DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
String data = node.getUserObject().toString();
System.out.println("WillCollapse: " + data);
}
#Override
public void treeWillExpand(
TreeExpansionEvent arg0) throws ExpandVetoException {
TreePath path = arg0.getPath();
DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
String data = node.getUserObject().toString();
System.out.println("WillExpand: " + data);
}
/**
* The model which this is viewing.
*/
private Model model;
/**
* Displays the contents of a database.
*/
private JTree browserTree;
}
My issue is that When I put the 'Server' Node in, it has no children, so it doesn't have a control to expand it. This means that 'treeWillExpand()' is never called for that node. How can I make it so that it has the expand control?
Also, what's the right terminology to use here as it seems like a very typical thing to do, but I couldn't find anything on Google.

As per aterai's answer:
treeModel.setAsksAllowsChildren(true)

Related

Select Node in TreeViewer

The lack of documentation for this really simple feature is disturbing. I have a TreeViewer and want to select a node. And the only way this makes sense is if the tree expands all elements up to the selection, else the user can't see it.
public class TreeWindow extends ApplicationWindow {
public static void main(String[] args) {
new TreeWindow().open();
}
public TreeWindow() {
super(null);
setBlockOnOpen(true);
}
#Override
protected Control createContents(Composite parent) {
final TreeViewer treeViewer = new TreeViewer(parent);
treeViewer.setContentProvider(new FileTreeContentProvider());
treeViewer.setLabelProvider(new LabelProvider() {
#Override
public String getText(Object element) {
String result = ((File) element).getName();
if (result.isEmpty()) {
result = ((File) element).getPath(); // root like C:\
}
return result;
}
});
treeViewer.setInput(File.listRoots());
// expand
final File fileToExpand = new File("src");
System.out.println("Expand to file: " + fileToExpand.getAbsolutePath());
return treeViewer.getControl();
}
static class FileTreeContentProvider extends ArrayContentProvider implements ITreeContentProvider {
#Override
public Object[] getChildren(Object parentElement) {
return ((File) parentElement).listFiles();
}
#Override
public Object getParent(Object element) {
return ((File) element).getParentFile();
}
#Override
public boolean hasChildren(Object element) {
return ((File) element).isDirectory();
}
}
}
What I tried:
treeViewer.setSelection(new StructuredSelection(fileToExpand));
System.out.println("Selection: " + treeViewer.getSelection());
The selection doesn't get set. (I saw multiple times that TreeViewer#setSelection(ISelection, boolean) was used, but the JavaDoc states "Currently the reveal parameter is not honored because Tree does not provide an API to only select an item without scrolling it into view").
treeViewer.expandToLevel(fileToExpand, AbstractTreeViewer.ALL_LEVELS);
This method... does nothing?
final Tree tree = treeViewer.getTree();
final TreeItem[] items = tree.getSelection();
for (int i = 0; i < items.length; ++i) {
final TreeItem item = items[i];
TreeItem treeParent = item.getParentItem();
while (treeParent != null) {
treeParent.setExpanded(true);
treeParent = treeParent.getParentItem();
}
}
Might work maybe? But the selection does not get set, so...
treeViewer.expandAll();
This method normally works, but I don't think it's a good idea to try it in the above example. It does not do what I want, so it's a moot point anyways.
The problem seems to be that the TreeItem is created lazily. To check that you can try this:
for (final TreeItem item : this.treeViewer.getTree().getItems()) {
System.out.println(item.getData() + " " + item.getItemCount());
}
This method outputs either 1 if the item has children or 0 if not, but not the actual item count. Also, if you try to get the children's data, it's null.
How do I select a node that is not expanded? How do I expand the tree to the selection / a specified node?
Possible duplicate:
How to expand a specific node in TreeViewer(org.eclipse.jface) (I'm not sure if this is the same problem, but there is no solution either way)
I was able to get this to work on JFace 3.13.2:
// element is any object of your tree content provider data model,
// in your case a File.
treeViewer.expandToLevel(element, 0);
treeViewer.setSelection(new StructuredSelection(element));
The above code will make all the nodes down to the level of the selected element expanded, scroll the relevant part of the tree into view and mark the element as selected.
In my case the tree was four levels deep and the selected element was a leaf node. If you wanted to expand levels below the selected node, you can provide a level higher than 0 as second parameter to expandToLevel, or TreeViewer.ALL_LEVELS to expand all levels in the subtree.

Java Quiz System, help required in changing the name of a node

Here's the problem:
I have three Classes. One is a DynamicTree, one is called "main application" which all other classes MUST pass through. The other is called "addQForm" which is basically a form to add a node to the Dynamic Tree.
So in my Dynamic Tree there is a group of functions which add a node to the tree. These are as follows.
public DefaultMutableTreeNode addObject(Object child) {
DefaultMutableTreeNode parentNode = null;
TreePath parentPath = tree.getSelectionPath();
if (parentPath == null) {
parentNode = rootNode;
}
else {
parentNode = (DefaultMutableTreeNode) (parentPath.getLastPathComponent());
}
return addObject(parentNode, child, true);
}
public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, Object child) {
return addObject(parent, child, false);
}
public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, Object child, boolean shouldBeVisible) {
DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(child);
if (parent == null) {
parent = rootNode;
}
treeModel.insertNodeInto(childNode, parent, parent.getChildCount());
if (shouldBeVisible) {
tree.scrollPathToVisible(new TreePath(childNode.getPath()));
}
return childNode;
}
Okay so from here, I have the mainApplication which creates a GUI and adds the Dynamic Tree to it.
treePanel = new DynamicTree();
populateTree(treePanel);
So the Main Application has a button which has the following code:
if (ADD_COMMAND.equals(command)) {
//Add button clicked
addQForm.main(null);
treePanel.addObject(" New Node" + newNodeSuffix);
The Main Application also has the function "add Node" which shows a message dialogue (just to check what is stored in the local variable qTitle).
It then calls the "add Object Function" from the Dynamic Tree.
public void addNode(String qTitle){
JOptionPane.showMessageDialog(null, qTitle, "Error Message",
JOptionPane.ERROR_MESSAGE);
treePanel.addObject(qTitle);
}
So in my third class I have a Text Field and a Button. When The button is clicked the text is extracted from the text field and the form is closed.
public void actionPerformed(ActionEvent e) {
PrintWriter out;
try {
qTitle = qtitle.getText();
out = new PrintWriter(qtitle.getText() + ".txt");
out.println(qtitle.getText());
out.println(q.getText());
out.println(sUnit);
out.close();
dispose();
MainApplication mainapplication = new MainApplication();
mainapplication.addNode(qTitle);
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
As you can see I called the function addNode from the main application by instantiating.
BUT the variable is passed through to Main Application as it should however the second line:
treePanel.addObject(qTitle);
Will not function. I have attempted accessing other methods within my Dynamic Tree Class so this is just one example.
Can someone please explain to me why it refuses to complete this line of code?
I figured the error is when I instantiate but I'm not sure why...

How to disable expand sign in Swing JTree?

I'm working in Swing and I would like to disable the expand (plus [+]) sign on a certain type of nodes.
Not sure how to do it because my nodes aren't leaves and I also cannot use setShowsRootHandles (which is only for the root).
I'm referring to to JTree: suppose i got this structure:
Root
--[+] node1
--[+] node2
when I load this structure i would like not to see the [+] sign on node2 (because it a special type node). But I also would like to expand it by using a special command.
I've overridden isLeaf() (method from DefaultMutableTreeNode) so it would set to to TRUE when i'm in the special type node, but then when I'm trying to expand it, it wouldn't expand because isLeaf() == TRUE...
Hope this will make things more clear.
While it is not possible to remove the handles, it is possible to restrict the expansion of nodes. The way to go is a TreeWillExpandListener combined with a custom treeNode that has state to restrict expansion:
the custom node below has an expandable property that's false by default
when detecting custom nodes, the listener allows/vetoes expansion based on that expandable property
for programmatic expansion, the expandable property is set to true temporarily to pass the listener
Example code:
// mixed tree of normal/restricted noded
DefaultMutableTreeNode root = new DefaultMutableTreeNode("root");
DefaultMutableTreeNode normalSubTree = new DefaultMutableTreeNode("normal");
normalSubTree.add(new DefaultMutableTreeNode("normalChild"));
MyNode restrictedSubTree = new MyNode("restrictedSubtree");
restrictedSubTree.add(new DefaultMutableTreeNode("restrictedChild"));
root.add(normalSubTree);
root.add(restrictedSubTree);
final JTree tree = new JTree(root);
// the listener which vetos expansion of MyNodes that are not expandable
TreeWillExpandListener l = new TreeWillExpandListener() {
#Override
public void treeWillExpand(TreeExpansionEvent event)
throws ExpandVetoException {
TreePath path = event.getPath();
if (path.getLastPathComponent() instanceof MyNode) {
if (!((MyNode) path.getLastPathComponent()).isExpandable()) {
throw new ExpandVetoException(event, "node not expandable");
}
}
}
#Override
public void treeWillCollapse(TreeExpansionEvent event)
throws ExpandVetoException {
}
};
tree.addTreeWillExpandListener(l);
Action expand = new AbstractAction("Expand") {
#Override
public void actionPerformed(ActionEvent e) {
TreePath selected = tree.getSelectionPath();
if (selected == null) return;
if (selected.getLastPathComponent() instanceof MyNode) {
MyNode last = (MyNode) selected.getLastPathComponent();
boolean old = last.isExpandable();
last.setExpandable(true);
tree.expandPath(selected);
last.setExpandable(old);
}
}
};
JXFrame frame = wrapWithScrollingInFrame(tree, "veto expand");
addAction(frame, expand);
show(frame);
}
// custom node which has an expandable property
public static class MyNode extends DefaultMutableTreeNode {
private boolean expandable;
public MyNode() {
this(null);
}
public MyNode(Object userObject) {
super(userObject);
}
public void setExpandable(boolean expandable) {
this.expandable = expandable;
}
public boolean isExpandable() {
return expandable;
}
}
It's possible to remove the handles - despite what others have mentioned.
I've attached a snippet on how to do this below. The key thing is to override shouldPaintExpandControl in BasicTreeUI.
jtree.setUI(new BasicTreeUI() {
#Override
protected boolean shouldPaintExpandControl(final TreePath path, final int row
, final boolean isExpanded, final boolean hasBeenExpanded, final boolean isLeaf)
{
boolean shouldDisplayExpandControl = false;
return shouldDisplayExpandControl;
}
This should really be documented in the JTree API but that's another issue.
Another approach to consider:
If you call DefaultTreeModel(TreeNode root, boolean asksAllowsChildren) the model will "ask" the nodes you insert if they are allowed to have children. If they cannot, it should not display the expand icon.
Be sure to override javax.swing.tree.TreeNode.getAllowsChildren() in your class.

JTree update nodes without collapsing

I have a Java SE 7 application that needs to have the JTree nodes updated. From the tutorial given by Oracle using this thread, there's no given hint on how I could update the label (displayed text of the node on the Tree) on code. Currently I am using DefaultTreeModel as the model of my JTree and DefaultMutableTreeNode as the nodes of the said Tree.
To further detail about the application I am working on, I am developing a chat facility having the contact(s) displayed with their availability status (whether Online, Offline, etc.) per account.
The question is, how can I update the displayed text of a particular node without (at most) removing it from it's parent and adding it on it's designated index. Like a DefaultMutableTreeNode.setText("<new label>")?
UPDATE : January 20, 2013
Redefined the question for clarifications.
Perhaps if you use 'nodeChanged()' instead of 'reload()' you will get the effect you desire.
There are a bunch of methods on the DefaultTreeModel class that cause various parts of the tree to be changed and redrawn. There are also other methods on DefaultTreeModel that only cause redrawing to take place.
You mentioned 'reload(node)' and commented that it causes the tree to collapse when you call it. 'reload' causes the entire sub-tree to be completely redrawn starting at that node. (But if that node isn't visible, it changes nothing.) That is called a 'structure change'.
'insertNodeInto()' and 'removeNodeFromParent()' modify the tree structure by adding or removing the node and then redrawing.
I think 'nodeChanged()' is the one you need since it just notifies the model that something changed in the node that will cause it to display differently. Perhaps the displayable text is now different than it was. Perhaps you changed the user object in the node. That's when you call 'nodeChanged()' on a node.
You should try 'nodeChanged()' in place of the 'reload()' call in your own code that was collapsing and in the example program vels4j provided. This might take care of the problem.
Note that there are also two other families of methods on the DefaultTreeModel that are used in other cases:
These methods work with the tree nodes and use the tree path to define where the change took place. They do not change the data structures underlying the tree but notify the model that something changed so it can notify the listeners that actually redraw things or otherwise respond to changes.
nodesWereInserted()
nodesWereRemovde()
nodesChanged()
nodeStructureChanged()
There are also a set of fire...() methods that are used internally to the DefaultTreeModel and any sub-classes you may create. They merely notify any listeners that something changed. Notice that they are protected.
May this simple and executable program help you to resolve your issue.
public class JTreeDemo extends JPanel
implements Runnable {
private JTree tree;
private DefaultTreeModel treeModel ;
private Random rnd = new Random();
private List<User> userList;
public JTreeDemo() {
super( );
//Create the nodes.
DefaultMutableTreeNode top =
new DefaultMutableTreeNode("Users");
treeModel = new DefaultTreeModel(top);
createNodes(top);
//Create a tree that allows one selection at a time.
tree = new JTree(treeModel);
tree.getSelectionModel().setSelectionMode
(TreeSelectionModel.SINGLE_TREE_SELECTION);
//Create the scroll pane and add the tree to it.
JScrollPane treeView = new JScrollPane(tree);
//Add the split pane to this panel.
add(treeView);
}
public String getRandomStatus() {
int nextInt = rnd.nextInt(100);
if( nextInt%2==0) {
return "Online";
} else {
return "Offline";
}
}
#Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
int nextInt = rnd.nextInt(10);
User user = userList.get(nextInt);
user.setStatus(getRandomStatus());
treeModel.nodeChanged(user);
} catch (InterruptedException ex) {
// handle it if necessary
}
}
}
private class User extends DefaultMutableTreeNode {
public String userName;
public String status;
public User(String name) {
userName = name;
}
public void setStatus(String status) {
this.status = status;
}
public String getStatus() {
return status;
}
#Override
public String toString() {
String color = status.equals("Online") ? "Green" : "Red";
return "<html><b color='"+color+"'>"+
userName +"-"+status +"</b></html>";
}
}
private void createNodes(DefaultMutableTreeNode top) {
userList = new ArrayList() ;
for(int i=0;i<10;i++) {
User u1 = new User("User " + (i+1));
u1.setStatus("Online");
top.add(u1);
userList.add(u1);
}
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("TreeDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Add content to the window.
JTreeDemo jTreeDemo = new JTreeDemo();
frame.add(jTreeDemo);
frame.pack();
frame.setVisible(true);
// update status randomly
Thread thread = new Thread(jTreeDemo);
thread.start();
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
I've added a Thread to update Status randomly, hope you can modify base on your need.
Output :
Edit:
1. Based on suggestion I've removed reload(node) and added tree model reload.
It's easy if nodes contains objects which are unique in the tree and have implemented method equals and hashCode (for example you show strings or object with unique ID from database). First of all you iterate over all expanded nodes and save objects from the nodes in a set. Then you perform update of the model. After update you iterate over all nodes and if they are in the set you expand the node in the tree.
If nodes are not unique - you need to save in the set the complete tree path (for example as list) and check it after update to expand the nodes.
If objects has neither equals nor hashCode (both these methods must be implemented) - this variant cannot be used.
Just for the record (I voted for Lee Meador), DefaultTreeModel#nodeChanged(javax.swing.tree.TreeNode) is the way to go:
public class TestFrame extends JFrame {
public TestFrame() {
//create gui with simple jtree (and DefaultTreeModel)
JButton changeBtn = new JButton();
final JTree jTree = new JTree();
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
changeBtn.setText("update selected node");
getContentPane().add(changeBtn, java.awt.BorderLayout.PAGE_END);
DefaultMutableTreeNode treeNode1 = new DefaultMutableTreeNode("root");
DefaultMutableTreeNode treeNode2 = new DefaultMutableTreeNode("blue");
treeNode1.add(treeNode2);
treeNode2 = new DefaultMutableTreeNode("violet");
DefaultMutableTreeNode treeNode3 = new DefaultMutableTreeNode("red");
treeNode2.add(treeNode3);
treeNode3 = new DefaultMutableTreeNode("yellow");
treeNode2.add(treeNode3);
treeNode1.add(treeNode2);
jTree.setModel(new DefaultTreeModel(treeNode1));
getContentPane().add(jTree, BorderLayout.CENTER);
pack();
//add listener to button, to change selected node on button click
changeBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
DefaultMutableTreeNode dmt = (DefaultMutableTreeNode)jTree.getSelectionPath().getLastPathComponent();
//update content/representation of selected node
dmt.setUserObject("My update: " + new Date());
//nodeChanged
((DefaultTreeModel) jTree.getModel()).nodeChanged(dmt);
}
});
}
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new TestFrame().setVisible(true);
}
});
}
}

Populate parent List elements based on child values

Consider the following code:
CLASS AuditProgressReport:
public class AuditProgressReport
{
private List<AuditProgressReport> audit_progress_reports = null;
private String name = null;
private String description = null;
private int compliant;
private int non_compliant;
private int not_completed ;
/**
*
*/
public AuditProgressReport()
{
super();
}
public AuditProgressReport(
String name_param,
int compliant_param,
int non_compliant_param,
int not_completed_param)
{
super();
this.name = name_param;
this.compliant = compliant_param;
this.non_compliant = non_compliant_param;
this.not_completed = not_completed_param;
}
public void addToCompliant(int compl_to_add_param)
{
this.compliant += compl_to_add_param;
}
public void addToNonCompliant(int non_compl_to_add_param)
{
this.non_compliant += non_compl_to_add_param;
}
public void addToNotCompleted(int not_compl_param)
{
this.not_completed += not_compl_param;
}
public void setAuditProgressReports(List<AuditProgressReport> report_category_nodes_param)
{
this.audit_progress_reports = report_category_nodes_param;
}
public List<AuditProgressReport> getAuditProgressReports()
{
return this.audit_progress_reports;
}
public void setCompliant(int compliantParam)
{
this.compliant = compliantParam;
}
public int getCompliant()
{
return this.compliant;
}
public void setNonCompliant(int nonCompliantParam)
{
this.non_compliant = nonCompliantParam;
}
public int getNonCompliant()
{
return this.non_compliant;
}
public void setNotCompleted(int notCompletedParam)
{
this.not_completed = notCompletedParam;
}
public int getNotCompleted()
{
return this.not_completed;
}
public void setName(String name_param)
{
this.name = name_param;
}
public String getName()
{
return this.name;
}
public void setDescription(String description_param)
{
this.description = description_param;
}
public String getDescription()
{
return this.description;
}
#Override
public String toString()
{
return ("Compliant["+this.compliant+
"] Non-Compliant["+this.non_compliant+
"] Not-Completed["+this.not_completed+"]");
}
}
And CLASS Tester:
public class Tester
{
public static void main(String[] args)
{
List<AuditProgressReport> main_level = new ArrayList<AuditProgressReport>();
AuditProgressReport ar_1_1 = new AuditProgressReport("ar_1_1",0,0,0);
AuditProgressReport ar_1_2 = new AuditProgressReport("ar_1_2",0,0,0);
AuditProgressReport ar_1_1_1 = new AuditProgressReport("ar_1_1_1",0,0,0);
AuditProgressReport ar_1_1_2 = new AuditProgressReport("ar_1_1_2",15,65,20);
AuditProgressReport ar_1_1_3 = new AuditProgressReport("ar_1_1_3",20,30,50);
AuditProgressReport ar_1_1_1_1 = new AuditProgressReport("ar_1_1_1_1",5,5,90);
AuditProgressReport ar_1_1_1_2 = new AuditProgressReport("ar_1_1_1_2",55,5,40);
AuditProgressReport ar_1_1_1_3 = new AuditProgressReport("ar_1_1_1_3",35,35,30);
List<AuditProgressReport> arl_1_1_1 = new ArrayList<AuditProgressReport>();
arl_1_1_1.add(ar_1_1_1_1);
arl_1_1_1.add(ar_1_1_1_2);
arl_1_1_1.add(ar_1_1_1_3);
ar_1_1_1.setAuditProgressReports(arl_1_1_1);
List<AuditProgressReport> arl_1_1 = new ArrayList<AuditProgressReport>();
arl_1_1.add(ar_1_1_1);
arl_1_1.add(ar_1_1_2);
arl_1_1.add(ar_1_1_3);
AuditProgressReport ar_1_2_1 = new AuditProgressReport("ar_1_2_1",10,30,60);
AuditProgressReport ar_1_2_2 = new AuditProgressReport("ar_1_2_2",20,20,60);
List<AuditProgressReport> arl_1_2 = new ArrayList<AuditProgressReport>();
arl_1_2.add(ar_1_2_1);
arl_1_2.add(ar_1_2_2);
ar_1_1.setAuditProgressReports(arl_1_1);
ar_1_2.setAuditProgressReports(arl_1_2);
main_level.add(ar_1_1);
main_level.add(ar_1_2);
Tester tester = new Tester();
for(AuditProgressReport prog_rep : main_level)
{
tester.populateParents(prog_rep, null);
}
//TODO Now check the values...
}
private void populateParents(
AuditProgressReport audit_progress_param,
AuditProgressReport parent_param)
{
List<AuditProgressReport> audit_progress =
audit_progress_param.getAuditProgressReports();
System.out.println("name["+audit_progress_param.getName()+"]");
if(parent_param != null)
{
int compl = audit_progress_param.getCompliant();
int nonCompl = audit_progress_param.getNonCompliant();
int notCompleted = audit_progress_param.getNotCompleted();
parent_param.addToCompliant(compl);
parent_param.addToNonCompliant(nonCompl);
parent_param.addToNotCompleted(notCompleted);
}
if(audit_progress != null && ! audit_progress.isEmpty())
{
for(AuditProgressReport prog_rep : audit_progress)
{
this.populateParents(prog_rep,audit_progress_param);
}
}
}
}
When you run this, you will note that the values of the parent elements in the list is updated with the sum of the values in the child list.
The problem I am facing is that I want to have it updated all the way through the tree instead of just the immediate parent.
Is there a pattern that would help me achieve this?
See illustration below:
Like others suggested I would use the Observer pattern. Each parent node listens for changes on the childrens.
But my solution differs from that of #zmf because if you have a a big tree with lot of children node and at each update you have to sum each value, you would spend a lot of processing time.
What if you send only the difference between the old value and the new value each time you update a child node. Let's make an example. You start with this tree:
[12]--+--[10]-----[10]
|
+--[ 2]--+--[ ]
|
+--[ 2]
and you update a children like this
[12]--+--[10]-----[10]
|
+--[ 2]--+--[ 3]
|
+--[ 2]
the node that gets updated with the value "3" send its change to the parent with the method call parent.updateNode(3). The parent have only to sum its current value (in this example "2") with the value it receives from the child node. So it will update to the value "5"
[12]--+--[10]-----[10]
|
+--[ 5]--+--[ 3]
|
+--[ 2]
the node with the new value "5" will call parent.updateNode(3) and the final solution will be
[15]--+--[10]-----[10]
|
+--[ 5]--+--[ 3]
|
+--[ 2]
IMHO this solution is better because each updateNode() method have only to sum its own current value with the change received from its child node and call its parent with the same value received. You do not have to get the value from each one of your children and sum all the values. This will save you a lot of time if you have a big tree. So in this example when you change the value from 0 to 3. You will get 2 call to parent.updateNode(3) and each parent will get updated.
public void updateNode(int value) {
if (value != this.value) {
this.value = value;
if (getParent() != null) {
int sum = 0;
for (Node n : getParent().getChildren()) {
sum += n.getValue();
}
getParent.updateNode(sum);
}
}
}
Other poster's suggested the use of the Observer pattern. The Observer pattern is a subset of a Pub/Sub pattern. I recommend using this over an Observer pattern.
The main difference between an Observer pattern and a Pub/Sub pattern is that in an Observer pattern, an Observer is both a publisher of ChangeEvents and a dispatcher of messages. It's essentially making every Observable into an EventDispatcher. In a traditional Pub/Sub pattern, Observables are only publisher's of ChangeEvents. ChangeEvents are published to a separate EventDispatchingService which handles what Subscribers the Events need to be sent to.
Attempting to track global changes with an Observer pattern is difficult to do. For example, if you want to count the number of times time the addToCompliant() method was called, you would have to add the Observer on every instance of the Observable. With an Event Pub/Sub, your observer class can just subscribe to listen on the type of ChangeEvent and it will receive all of them. The best (IMHO) Event Pub/Sub library I've used is Google Guava's Event Bus. In your particular case, I'd do something like the following.
public class EventBusSingleton {
public static final EventBus INSTANCE = new EventBus("My Event Bus");
}
public class ComplianceChange {
private AuditProgressReport changedReport;
private int delta;
public ComplianceChange(AuditProgressReport changedReport, int delta) {
this.changedReport = changedReport;
this.delta = delta;
}
...
}
public class AuditProgressReport {
...
private AuditProgressReport parent;
public AuditProgressReport getParent() {
return parent;
}
public void addToCompliant(int delta) {
this.compliant += delta;
ComplianceChange change = new ComplianceChange(this, delta);
EventBusSingleton.INSTANCE.post(change);
}
...
}
public class ComplianceChangeHandler {
#Subscribe
public void notifyParent(ComplianceChange event) {
AuditProgressReport parent = event.getChangedReport().getParent();
int delta = event.getDelta();
parent.addToCompliant(delta);
}
#Subscribe
public void somethingElse(ComplianceChange event) {
// Do Something Else
}
}
// Somewhere during initialization
EventBusSingleton.INSTANCE.register(new ComplianceChangeHandler());
Based on your class name, I guess you want to see your audit progression live when running. So my hypothesis:
the tree structure does not change too much, almost fixed after creation
node values change often, counters initial states are 0
Here is an efficient implementation:
each node maintains the full list of its parent nodes
nodes are inserted with 0 value
when a node value is changed or simply increased, the parents' values from the node's list are updated by applying the delta between the previous node value
As a consequence the structure is always up-to-date, node insertion is still possible and does not impact the existing nodes.
If many audit threads run concurrently and report values into the structure, you have to take care to concurrency issues and use AtomicInteger as counter holders.
This is a pragmatic design and sincerely I have not found any matching pattern. Like for sort algorithms, trying to use patterns in such a context may be counter-productive.

Categories