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