View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.model;
21  
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  
25  import javax.swing.tree.DefaultMutableTreeNode;
26  import javax.swing.tree.TreeNode;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Stack;
33  
34  /**
35   * Implementation for a {@link TreeNode} that represents the data model for a tree.
36   */
37  public class TreeNodeDataModel extends TreeDataModel {
38  
39    private static final Logger LOG = LoggerFactory.getLogger(TreeNodeDataModel.class);
40  
41    private TreeNode data;
42    private int rowIndex = -1;
43    private Map<Integer, Data> mapping;
44    private Map<TreeNode, Integer> back;
45    private boolean showRoot;
46    private ExpandedState expandedState;
47  
48    /**
49     * @param data          The tree data, which shall be wrapped.
50     * @param showRoot      Is the root node visible.
51     * @param expandedState Defines which nodes are expanded, (XXX should it be so?) a value of {@code null} means all.
52     */
53    public TreeNodeDataModel(final TreeNode data, final boolean showRoot, final ExpandedState expandedState) {
54      this.data = data;
55      this.showRoot = showRoot;
56      this.mapping = new HashMap<Integer, Data>();
57      this.back = new HashMap<TreeNode, Integer>();
58      this.expandedState = expandedState;
59      reset();
60    }
61  
62    @Override
63    public void reset() {
64      this.mapping.clear();
65      this.back.clear();
66      TreeNode current = data;
67      for (int counter = back.size(); current != null; counter++) {
68  
69        mapping.put(counter, new Data(current));
70        back.put(current, counter);
71  
72        // if the node has children and is expanded, go to the children
73        if (current.getChildCount() > 0 && expandedState.isExpanded(current)) {
74          current = current.getChildAt(0);
75        } else {
76          current = getNextNodeButNoChild(current);
77        }
78      }
79    }
80  
81    @Override
82    public void update(final ExpandedState expandedState) {
83      this.expandedState = expandedState;
84      TreeNode current = data;
85      int counter = back.size();
86      while (current != null) {
87  
88        if (!back.containsKey(current)) {
89          mapping.put(counter, new Data(current));
90          back.put(current, counter);
91          counter++;
92        }
93  
94        // if the node has children and is expanded, go to the children
95        if (current.getChildCount() > 0 && expandedState.isExpanded(current)) {
96          current = current.getChildAt(0);
97        } else {
98          current = getNextNodeButNoChild(current);
99        }
100     }
101   }
102 
103   private TreeNode getNextNodeButNoChild(TreeNode node) {
104     TreeNode next;
105     while (true) {
106       next = nextSibling(node);
107       if (next != null) {
108         break;
109       }
110       node = node.getParent();
111       if (node == null) {
112         return null;
113       }
114 
115     }
116     return next;
117   }
118 
119   private TreeNode nextSibling(final TreeNode node) {
120     final TreeNode parent = node.getParent();
121     if (parent == null) {
122       return null;
123     }
124     for (int i = 0; i < parent.getChildCount() - 1; i++) {
125       if (parent.getChildAt(i) == node) { // == is okay in this case
126         return parent.getChildAt(i + 1);
127       }
128     }
129     return null;
130   }
131 
132   @Override
133   public int getRowCount() {
134     return mapping.size();
135   }
136 
137   @Override
138   public TreeNode getRowData() {
139     return mapping.get(rowIndex).getNode();
140   }
141 
142   @Override
143   public int getRowIndex() {
144     return rowIndex;
145   }
146 
147   @Override
148   public int getLevel() {
149     int count = -1;
150     for (TreeNode node = getRowData(); node != null; node = node.getParent()) {
151       count++;
152     }
153     return count;
154   }
155 
156   @Override
157   public TreePath getPath() {
158     return new TreePath(getRowData());
159   }
160 
161   @Override
162   public int getDepth() {
163     if (data instanceof DefaultMutableTreeNode) {
164       return ((DefaultMutableTreeNode) data).getDepth();
165     }
166     return -1;
167   }
168 
169   @Override
170   public boolean isFolder() {
171     return !getRowData().isLeaf();
172   }
173 
174   @Override
175   public Object getWrappedData() {
176     return data;
177   }
178 
179   @Override
180   public boolean isRowAvailable() {
181     return 0 <= rowIndex && rowIndex < getRowCount();
182   }
183 
184   @Override
185   public void setRowIndex(final int rowIndex) {
186     this.rowIndex = rowIndex;
187   }
188 
189   @Override
190   public void setWrappedData(final Object data) {
191     this.data = (TreeNode) data;
192   }
193 
194   @Override
195   public boolean isRowVisible() {
196     if (!isRowAvailable()) {
197       return false;
198     }
199     final TreeNode start = getRowData();
200     if (start.getParent() == null) {
201       return showRoot;
202     }
203     TreeNode node = start.getParent();
204     while (node != null && back.get(node) != null) {
205       final Data data = mapping.get(back.get(node));
206       if (data.getNode().getParent() == null && !showRoot) {
207         return true;
208       }
209       if (!expandedState.isExpanded(new TreePath(node))) {
210         return false;
211       }
212       node = node.getParent();
213     }
214     return true;
215   }
216 
217   @Override
218   public String getRowClientId() {
219     if (isRowAvailable()) {
220       return mapping.get(rowIndex).getClientId();
221     } else {
222       return null;
223     }
224   }
225 
226   @Override
227   public void setRowClientId(final String clientId) {
228     if (isRowAvailable()) {
229       mapping.get(rowIndex).setClientId(clientId);
230     } else {
231       LOG.warn("No row index set: clientId='" + clientId + "'");
232     }
233   }
234 
235   @Override
236   public String getRowParentClientId() {
237     if (isRowAvailable()) {
238       final TreeNode parent = mapping.get(rowIndex).getNode().getParent();
239       if (parent != null && back.get(parent) != null) {
240         return mapping.get(back.get(parent)).getClientId();
241       } else {
242         return null;
243       }
244     } else {
245       return null;
246     }
247   }
248 
249   @Override
250   public List<Integer> getRowIndicesOfChildren() {
251     final TreeNode node = getRowData();
252     final int n = node.getChildCount();
253     final List<Integer> children = new ArrayList<Integer>(n);
254     for (int i = 0; i < n; i++) {
255       final Integer integer = back.get(node.getChildAt(i));
256       if (integer != null) { // integer == null happens, when the node is not expanded
257         children.add(integer); // XXX is this a good way to handle that case?
258       }
259     }
260     return children;
261   }
262 
263   @Override
264   public List<Boolean> getJunctions() {
265     TreeNode node = getRowData();
266     final List<Boolean> junctions = new Stack<Boolean>();
267     while (node != null) {
268       junctions.add(hasNextSibling(node));
269       node = node.getParent();
270     }
271     Collections.reverse(junctions);
272     return junctions;
273   }
274 
275   private boolean hasNextSibling(final TreeNode node) {
276     final TreeNode parent = node.getParent();
277     return parent != null && parent.getChildAt(parent.getChildCount() - 1) != node;
278   }
279 
280   /**
281    * Here we cache some state information of the nodes, because we can't access the UITreeNode state of the other nodes
282    * while rendering.
283    */
284   private static class Data {
285 
286     private TreeNode node;
287     private String clientId;
288 
289     private Data(final TreeNode node) {
290       this.node = node;
291     }
292 
293     public TreeNode getNode() {
294       return node;
295     }
296 
297     public String getClientId() {
298       return clientId;
299     }
300 
301     public void setClientId(final String clientId) {
302       this.clientId = clientId;
303     }
304   }
305 }