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   public TreeNode getRowData() {
138     return mapping.get(rowIndex).getNode();
139   }
140 
141   @Override
142   public int getRowIndex() {
143     return rowIndex;
144   }
145 
146   @Override
147   public int getLevel() {
148     int count = -1;
149     for (TreeNode node = getRowData(); node != null; node = node.getParent()) {
150       count++;
151     }
152     return count;
153   }
154 
155   @Override
156   public TreePath getPath() {
157     return new TreePath(getRowData());
158   }
159 
160   @Override
161   public int getDepth() {
162     if (data instanceof DefaultMutableTreeNode) {
163       return ((DefaultMutableTreeNode) data).getDepth();
164     }
165     return -1;
166   }
167 
168   @Override
169   public boolean isFolder() {
170     return !getRowData().isLeaf();
171   }
172 
173   @Override
174   public Object getWrappedData() {
175     return data;
176   }
177 
178   @Override
179   public boolean isRowAvailable() {
180     return 0 <= rowIndex && rowIndex < getRowCount();
181   }
182 
183   @Override
184   public void setRowIndex(final int rowIndex) {
185     this.rowIndex = rowIndex;
186   }
187 
188   @Override
189   public void setWrappedData(final Object data) {
190     this.data = (TreeNode) data;
191   }
192 
193   @Override
194   public boolean isRowVisible() {
195     if (!isRowAvailable()) {
196       return false;
197     }
198     final TreeNode start = getRowData();
199     if (start.getParent() == null) {
200       return showRoot;
201     }
202     TreeNode node = start.getParent();
203     while (node != null && back.get(node) != null) {
204       final Data data = mapping.get(back.get(node));
205       if (data.getNode().getParent() == null && !showRoot) {
206         return true;
207       }
208       if (!expandedState.isExpanded(new TreePath(node))) {
209         return false;
210       }
211       node = node.getParent();
212     }
213     return true;
214   }
215 
216   public String getRowClientId() {
217     if (isRowAvailable()) {
218       return mapping.get(rowIndex).getClientId();
219     } else {
220       return null;
221     }
222   }
223 
224   public void setRowClientId(final String clientId) {
225     if (isRowAvailable()) {
226       mapping.get(rowIndex).setClientId(clientId);
227     } else {
228       LOG.warn("No row index set: clientId='" + clientId + "'");
229     }
230   }
231 
232   public String getRowParentClientId() {
233     if (isRowAvailable()) {
234       final TreeNode parent = mapping.get(rowIndex).getNode().getParent();
235       if (parent != null && back.get(parent) != null) {
236         return mapping.get(back.get(parent)).getClientId();
237       } else {
238         return null;
239       }
240     } else {
241       return null;
242     }
243   }
244 
245   public List<Integer> getRowIndicesOfChildren() {
246     final TreeNode node = getRowData();
247     final int n = node.getChildCount();
248     final List<Integer> children = new ArrayList<Integer>(n);
249     for (int i = 0; i < n; i++) {
250       final Integer integer = back.get(node.getChildAt(i));
251       if (integer != null) { // integer == null happens, when the node is not expanded
252         children.add(integer); // XXX is this a good way to handle that case?
253       }
254     }
255     return children;
256   }
257 
258   @Override
259   public List<Boolean> getJunctions() {
260     TreeNode node = getRowData();
261     final List<Boolean> junctions = new Stack<Boolean>();
262     while (node != null) {
263       junctions.add(hasNextSibling(node));
264       node = node.getParent();
265     }
266     Collections.reverse(junctions);
267     return junctions;
268   }
269 
270   private boolean hasNextSibling(final TreeNode node) {
271     final TreeNode parent = node.getParent();
272     return parent != null && parent.getChildAt(parent.getChildCount() - 1) != node;
273   }
274 
275   /**
276    * Here we cache some state information of the nodes, because we can't access the UITreeNode state of the other nodes
277    * while rendering.
278    */
279   private static class Data {
280 
281     private TreeNode node;
282     private String clientId;
283 
284     private Data(final TreeNode node) {
285       this.node = node;
286     }
287 
288     public TreeNode getNode() {
289       return node;
290     }
291 
292     public String getClientId() {
293       return clientId;
294     }
295 
296     public void setClientId(final String clientId) {
297       this.clientId = clientId;
298     }
299   }
300 }