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