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.TreeNode;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Stack;
32  
33  /**
34   * Implementation for a {@link TreeNode} that represents the data model for a tree.
35   */
36  public class TreeNodeDataModel extends TreeDataModel {
37  
38    private static final Logger LOG = LoggerFactory.getLogger(TreeNodeDataModel.class);
39  
40    private TreeNode data;
41    private int rowIndex = -1;
42    private Map<Integer, Data> mapping;
43    private Map<TreeNode, Integer> back;
44    private boolean showRoot;
45    private ExpandedState expandedState;
46  
47    /**
48     * @param data          The tree data, which shall be wrapped.
49     * @param showRoot      Is the root node visible.
50     * @param expandedState Defines which nodes are expanded, (XXX should it be so?) a value of {@code null} means all.
51     */
52    public TreeNodeDataModel(TreeNode data, boolean showRoot, ExpandedState expandedState) {
53      this.data = data;
54      this.showRoot = showRoot;
55      this.mapping = new HashMap<Integer, Data>();
56      this.back = new HashMap<TreeNode, Integer>();
57      this.expandedState = expandedState;
58      reset();
59    }
60  
61    @Override
62    public void reset() {
63      this.mapping.clear();
64      this.back.clear();
65      TreeNode current = data;
66      for (int counter = back.size(); current != null; counter++) {
67  
68        mapping.put(counter, new Data(current));
69        back.put(current, counter);
70  
71        // if the node has children and is expanded, go to the children
72        if (current.getChildCount() > 0 && expandedState.isExpanded(current)) {
73          current = current.getChildAt(0);
74        } else {
75          current = getNextNodeButNoChild(current);
76        }
77      }
78    }
79  
80    @Override
81    public void update(ExpandedState expandedState) {
82      this.expandedState = expandedState;
83      TreeNode current = data;
84      int counter = back.size();
85      while (current != null) {
86  
87        if (!back.containsKey(current)) {
88          mapping.put(counter, new Data(current));
89          back.put(current, counter);
90          counter++;
91        }
92  
93        // if the node has children and is expanded, go to the children
94        if (current.getChildCount() > 0 && expandedState.isExpanded(current)) {
95          current = current.getChildAt(0);
96        } else {
97          current = getNextNodeButNoChild(current);
98        }
99      }
100   }
101 
102   private TreeNode getNextNodeButNoChild(TreeNode node) {
103     TreeNode next;
104     while (true) {
105       next = nextSibling(node);
106       if (next != null) {
107         break;
108       }
109       node = node.getParent();
110       if (node == null) {
111         return null;
112       }
113 
114     }
115     return next;
116   }
117 
118   private TreeNode nextSibling(TreeNode node) {
119     TreeNode parent = node.getParent();
120     if (parent == null) {
121       return null;
122     }
123     for (int i = 0; i < parent.getChildCount() - 1; i++) {
124       if (parent.getChildAt(i) == node) { // == is okay in this case
125         return parent.getChildAt(i + 1);
126       }
127     }
128     return null;
129   }
130 
131   @Override
132   public int getRowCount() {
133     return mapping.size();
134   }
135 
136   public TreeNode getRowData() {
137     return mapping.get(rowIndex).getNode();
138   }
139 
140   @Override
141   public int getRowIndex() {
142     return rowIndex;
143   }
144 
145   @Override
146   public int getLevel() {
147     int count = -1;
148     for (TreeNode node = getRowData(); node != null; node = node.getParent()) {
149       count++;
150     }
151     return count;
152   }
153 
154   @Override
155   public TreePath getPath() {
156     return new TreePath(getRowData());
157   }
158 
159   @Override
160   public boolean isFolder() {
161     return !getRowData().isLeaf();
162   }
163 
164   @Override
165   public Object getWrappedData() {
166     return data;
167   }
168 
169   @Override
170   public boolean isRowAvailable() {
171     return 0 <= rowIndex && rowIndex < getRowCount();
172   }
173 
174   @Override
175   public void setRowIndex(int rowIndex) {
176     this.rowIndex = rowIndex;
177   }
178 
179   @Override
180   public void setWrappedData(Object data) {
181     this.data = (TreeNode) data;
182   }
183 
184   @Override
185   public boolean isRowVisible() {
186     if (!isRowAvailable()) {
187       return false;
188     }
189     final TreeNode start = getRowData();
190     if (start.getParent() == null) {
191       return showRoot;
192     }
193     TreeNode node = start.getParent();
194     while (node != null) {
195       final Data data = mapping.get(back.get(node));
196       if (data.getNode().getParent() == null && !showRoot) {
197         return true;
198       }
199       if (!expandedState.isExpanded(new TreePath(node))) {
200         return false;
201       }
202       node = node.getParent();
203     }
204     return true;
205   }
206 
207   public String getRowClientId() {
208     if (isRowAvailable()) {
209       return mapping.get(rowIndex).getClientId();
210     } else {
211       return null;
212     }
213   }
214 
215   public void setRowClientId(String clientId) {
216     if (isRowAvailable()) {
217       mapping.get(rowIndex).setClientId(clientId);
218     } else {
219       LOG.warn("No row index set: clientId='" + clientId + "'");
220     }
221   }
222 
223   public String getRowParentClientId() {
224     if (isRowAvailable()) {
225       final TreeNode parent = mapping.get(rowIndex).getNode().getParent();
226       if (parent != null) {
227         return mapping.get(back.get(parent)).getClientId();
228       } else {
229         return null;
230       }
231     } else {
232       return null;
233     }
234   }
235 
236   public List<Integer> getRowIndicesOfChildren() {
237     final TreeNode node = getRowData();
238     final int n = node.getChildCount();
239     final List<Integer> children = new ArrayList<Integer>(n);
240     for (int i = 0; i < n; i++) {
241       final Integer integer = back.get(node.getChildAt(i));
242       if (integer != null) { // integer == null happens, when the node is not expanded
243         children.add(integer); // XXX is this a good way to handle that case?
244       }
245     }
246     return children;
247   }
248 
249   @Override
250   public List<Boolean> getJunctions() {
251     TreeNode node = getRowData();
252     final List<Boolean> junctions = new Stack<Boolean>();
253     while (node != null) {
254       junctions.add(hasNextSibling(node));
255       node = node.getParent();
256     }
257     Collections.reverse(junctions);
258     return junctions;
259   }
260 
261   private boolean hasNextSibling(TreeNode node) {
262     final TreeNode parent = node.getParent();
263     return parent != null && parent.getChildAt(parent.getChildCount() - 1) != node;
264   }
265 
266   /**
267    * Here we cache some state information of the nodes, because we can't access the UITreeNode state of the other nodes
268    * while rendering.
269    */
270   private static class Data {
271 
272     private TreeNode node;
273     private String clientId;
274 
275     private Data(TreeNode node) {
276       this.node = node;
277     }
278 
279     public TreeNode getNode() {
280       return node;
281     }
282 
283     public String getClientId() {
284       return clientId;
285     }
286 
287     public void setClientId(String clientId) {
288       this.clientId = clientId;
289     }
290   }
291 }