001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.extensions.markup.html.repeater.util;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.Iterator;
022import java.util.List;
023
024import javax.swing.event.TreeModelEvent;
025import javax.swing.event.TreeModelListener;
026import javax.swing.tree.TreeModel;
027
028import org.apache.wicket.ajax.AjaxRequestTarget;
029import org.apache.wicket.extensions.markup.html.repeater.tree.AbstractTree;
030import org.apache.wicket.extensions.markup.html.repeater.tree.ITreeProvider;
031import org.apache.wicket.model.IModel;
032
033/**
034 * A provider wrapping a Swing {@link TreeModel}.
035 * <br>
036 * EXPERIMENTAL !
037 * 
038 * @author svenmeier
039 * @param <T>
040 *            model object type
041 */
042public abstract class TreeModelProvider<T> implements ITreeProvider<T>
043{
044
045        private static final long serialVersionUID = 1L;
046
047        private final TreeModel treeModel;
048
049        private final boolean rootVisible;
050
051        protected boolean completeUpdate;
052
053        protected List<T> nodeUpdates;
054
055        protected List<T> branchUpdates;
056
057        /**
058         * Wrap the given {@link TreeModel}.
059         * 
060         * @param treeModel
061         *            model to wrap
062         */
063        public TreeModelProvider(TreeModel treeModel)
064        {
065                this(treeModel, true);
066        }
067
068        /**
069         * Wrap the given {@link TreeModel}.
070         * 
071         * @param treeModel
072         *            the wrapped model
073         * @param rootVisible
074         *            should the root be visible
075         */
076        public TreeModelProvider(TreeModel treeModel, boolean rootVisible)
077        {
078                this.treeModel = treeModel;
079                this.rootVisible = rootVisible;
080
081                treeModel.addTreeModelListener(new Listener());
082        }
083
084        @Override
085        public Iterator<T> getRoots()
086        {
087                if (rootVisible)
088                {
089                        return new Iterator<T>()
090                        {
091                                boolean next = true;
092
093                                @Override
094                                public boolean hasNext()
095                                {
096                                        return next;
097                                }
098
099                                @Override
100                                public T next()
101                                {
102                                        next = false;
103                                        return cast(treeModel.getRoot());
104                                }
105
106                                @Override
107                                public void remove()
108                                {
109                                        throw new UnsupportedOperationException();
110                                }
111                        };
112                }
113                else
114                {
115                        return getChildren(cast(treeModel.getRoot()));
116                }
117        }
118
119        @Override
120        public boolean hasChildren(T object)
121        {
122                return !treeModel.isLeaf(object);
123        }
124
125        @Override
126        public Iterator<T> getChildren(final T object)
127        {
128                return new Iterator<T>()
129                {
130                        private int size = treeModel.getChildCount(object);
131                        private int index = -1;
132
133                        @Override
134                        public boolean hasNext()
135                        {
136                                return index < size - 1;
137                        }
138
139                        @Override
140                        public T next()
141                        {
142                                index++;
143                                return cast(treeModel.getChild(object, index));
144                        }
145
146                        @Override
147                        public void remove()
148                        {
149                                throw new UnsupportedOperationException();
150                        }
151                };
152        }
153
154        @SuppressWarnings("unchecked")
155        protected T cast(Object object)
156        {
157                return (T)object;
158        }
159
160        @Override
161        public abstract IModel<T> model(T object);
162
163        @Override
164        public void detach()
165        {
166                completeUpdate = false;
167                nodeUpdates = null;
168                branchUpdates = null;
169        }
170
171        /**
172         * Call this method after all change to the wrapped {@link TreeModel} being initiated via
173         * {@link AjaxRequestTarget}.
174         * 
175         * @param tree
176         *            the tree utilizing this {@link ITreeProvider}
177         * @param target
178         *            the {@link AjaxRequestTarget} which initiated the changes
179         */
180        public void update(AbstractTree<T> tree, AjaxRequestTarget target)
181        {
182                if (completeUpdate)
183                {
184                        target.add(tree);
185                }
186                else
187                {
188                        if (nodeUpdates != null)
189                        {
190                                for (T object : nodeUpdates)
191                                {
192                                        tree.updateNode(object, target);
193                                }
194                        }
195
196                        if (branchUpdates != null)
197                        {
198                                for (T object : branchUpdates)
199                                {
200                                        tree.updateBranch(object, target);
201                                }
202                        }
203                }
204
205                detach();
206        }
207
208        protected void nodeUpdate(Object[] nodes)
209        {
210                if (nodeUpdates == null)
211                {
212                        nodeUpdates = new ArrayList<>();
213                }
214
215                for (Object node : nodes)
216                {
217                        nodeUpdates.add(cast(node));
218                }
219        }
220
221        protected void branchUpdate(Object branch)
222        {
223                if (branchUpdates == null)
224                {
225                        branchUpdates = new ArrayList<>();
226                }
227
228                branchUpdates.add(cast(branch));
229        }
230
231        private class Listener implements TreeModelListener, Serializable
232        {
233                private static final long serialVersionUID = 1L;
234
235                @Override
236                public void treeNodesChanged(TreeModelEvent e)
237                {
238                        if (e.getChildIndices() == null)
239                        {
240                                completeUpdate = true;
241                        }
242                        else
243                        {
244                                nodeUpdate(e.getChildren());
245                        }
246                }
247
248                @Override
249                public void treeNodesInserted(TreeModelEvent e)
250                {
251                        branchUpdate(e.getTreePath().getLastPathComponent());
252                }
253
254                @Override
255                public void treeNodesRemoved(TreeModelEvent e)
256                {
257                        branchUpdate(e.getTreePath().getLastPathComponent());
258                }
259
260                @Override
261                public void treeStructureChanged(TreeModelEvent e)
262                {
263                        if (e.getTreePath().getPathCount() == 1)
264                        {
265                                completeUpdate = true;
266                        }
267                        else
268                        {
269                                branchUpdate(e.getTreePath().getLastPathComponent());
270                        }
271                }
272        }
273}