001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ade.galleries;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsResourceFilter;
032import org.opencms.jsp.CmsJspNavBuilder;
033import org.opencms.jsp.CmsJspNavBuilder.Visibility;
034import org.opencms.jsp.CmsJspNavElement;
035
036import java.util.List;
037
038import com.google.common.collect.Lists;
039
040/**
041 * Helper class for building a filtered sitemap tree for the gallery dialog's 'Sitemap' tab.<p>
042 *
043 * Objects of this class are single-use, which means they should only be used for a single navigation tree filtering operation.
044 */
045public class CmsGalleryFilteredNavTreeBuilder {
046
047    /**
048     * A tree node representing a navigation entry.<p>
049     */
050    public class NavigationNode {
051
052        /** The child nodes. */
053        private List<NavigationNode> m_children = Lists.newArrayList();
054
055        /** True if this is a leaf node in the original, unfiltered navigation tree. */
056        private boolean m_isLeaf;
057
058        /** True if this node has matched a filter string. */
059        private boolean m_isMatch;
060
061        /** True if this node should not be thrown away. */
062        private boolean m_keep;
063
064        /** The navigation element for this node. */
065        private CmsJspNavElement m_navElement;
066
067        /** The parent node. */
068        private NavigationNode m_parent;
069
070        /**
071         * Creates a new node.<p>
072         *
073         * @param navElement the navigation element
074         */
075        NavigationNode(CmsJspNavElement navElement) {
076            m_navElement = navElement;
077        }
078
079        /**
080         * Gets the children of this node.<p>
081         *
082         * @return the list of child nodes
083         */
084        public List<NavigationNode> getChildren() {
085
086            return m_children;
087        }
088
089        /**
090         * Gets the navigation element for this node.<p>
091         *
092         * @return the navigation element
093         */
094        public CmsJspNavElement getNavElement() {
095
096            return m_navElement;
097        }
098
099        /**
100         * Returns true if this is a leaf in the original unfiltered navigation tree.<p>
101         *
102         * @return true if this is a leaf
103         */
104        public boolean isLeaf() {
105
106            return m_isLeaf;
107        }
108
109        /**
110         * Returns true if this node has previously matched the filter string.<P>
111         *
112         * @return true if this has matched the filter string
113         */
114        public boolean isMatch() {
115
116            return m_isMatch;
117        }
118
119        /**
120         * Removes the node from its parent.<p>
121         */
122        public void removeFromParent() {
123
124            if (m_parent != null) {
125                m_parent.m_children.remove(this);
126                m_parent = null;
127            }
128        }
129
130        /**
131         * Marks this node as a leaf.<p>
132         *
133         * @param isLeaf true if this should be marked as a leaf
134         */
135        public void setIsLeaf(boolean isLeaf) {
136
137            m_isLeaf = isLeaf;
138        }
139
140        /**
141         * @see java.lang.Object#toString()
142         */
143        @Override
144        public String toString() {
145
146            return m_navElement == null ? super.toString() : m_navElement.getResource().getRootPath();
147        }
148
149        /**
150         * Collects all tree leaves.<p>
151         *
152         * @param results the list used to collect the leaves
153         */
154        void collectLeaves(List<NavigationNode> results) {
155
156            if (m_children.isEmpty()) {
157                results.add(this);
158            } else {
159                for (NavigationNode child : m_children) {
160                    child.collectLeaves(results);
161
162                }
163            }
164        }
165
166        /**
167         * Creates children for this node by loading child navigation entries for this node's navigation entry.<p<
168         */
169        @SuppressWarnings("synthetic-access")
170        void expand() {
171
172            for (CmsJspNavElement navElement : m_navBuilder.getNavigationForFolder(
173                m_navElement.getResource().getRootPath(),
174                Visibility.all,
175                CmsResourceFilter.ONLY_VISIBLE)) {
176                if ((navElement != null) && navElement.isInNavigation()) {
177                    NavigationNode child = new NavigationNode(navElement);
178                    addChild(child);
179                }
180            }
181
182        }
183
184        /**
185         * Recursively loads children for all nodes.<p>
186         */
187        void expandAll() {
188
189            expand();
190            for (NavigationNode child : m_children) {
191                child.expandAll();
192            }
193        }
194
195        /**
196         * Gets the leaves of this tree.<p<
197         *
198         * @return the leaves of the tree
199         */
200        List<NavigationNode> getLeaves() {
201
202            List<NavigationNode> result = Lists.newArrayList();
203            collectLeaves(result);
204            return result;
205        }
206
207        /**
208         * Gets the parent node.<p>
209         *
210         * @return the parent node
211         */
212        NavigationNode getParent() {
213
214            return m_parent;
215        }
216
217        /**
218         * Marks the node to not be removed during the pruning step.<p>
219         */
220        void markAsKeep() {
221
222            m_keep = true;
223        }
224
225        /**
226         * Matches all nodes of the tree against the filter.<p>
227         *
228         * @param filter the filter string
229         */
230        @SuppressWarnings("synthetic-access")
231        void matchAll(String filter) {
232
233            m_isMatch = matches(filter);
234            CmsGalleryFilteredNavTreeBuilder.this.m_hasMatches |= m_isMatch;
235            for (NavigationNode child : m_children) {
236                child.matchAll(filter);
237            }
238
239        }
240
241        /**
242         * Checks if the node matches the given filter string.<p>
243         *
244         * @param filter a filter string
245         *
246         * @return true if the node matches the filter string
247         */
248        boolean matches(String filter) {
249
250            for (String matchText : new String[] {
251                m_navElement.getNavText(),
252                m_navElement.getTitle(),
253                m_navElement.getDescription()}) {
254                if (matchText != null) {
255                    return matchText.toLowerCase().contains(filter.toLowerCase());
256                }
257            }
258            return true;
259        }
260
261        /**
262         * Prunes nodes which should not be kept after filtering.<p>
263         */
264        void removeUnmarkedNodes() {
265
266            if (!m_keep) {
267                removeFromParent();
268            } else {
269                for (NavigationNode child : Lists.newArrayList(m_children)) {
270                    child.removeUnmarkedNodes();
271                }
272            }
273        }
274
275        /**
276         * Adds a child node.<p>
277         *
278         * @param child the child node
279         */
280        private void addChild(NavigationNode child) {
281
282            if (child.m_parent != null) {
283                throw new IllegalArgumentException();
284            }
285            m_children.add(child);
286            child.m_parent = this;
287        }
288    }
289
290    /** True if any matches have been found. */
291    private boolean m_hasMatches;
292
293    /** The navigation builder used to process the navigation entries. */
294    private CmsJspNavBuilder m_navBuilder;
295
296    /** The root node. */
297    private NavigationNode m_root;
298
299    /**
300     * Creates a new navigation tree.<p>
301     *
302     * @param cms the CMS context
303     * @param rootPath the root path
304     */
305    public CmsGalleryFilteredNavTreeBuilder(CmsObject cms, String rootPath) {
306        CmsJspNavBuilder navBuilder = new CmsJspNavBuilder(cms);
307        m_navBuilder = navBuilder;
308        CmsJspNavElement rootNav = navBuilder.getNavigationForResource(
309            rootPath,
310            CmsResourceFilter.ONLY_VISIBLE_NO_DELETED);
311        m_root = new NavigationNode(rootNav);
312    }
313
314    /**
315     * Gets the root node.<p>
316     *
317     * @return the root node
318     */
319    public NavigationNode getRoot() {
320
321        return m_root;
322    }
323
324    /**
325     * Returns true if any matching tree nodes have been found.<p>
326     *
327     * @return tree if any matching tree nodes have been found
328     */
329    public boolean hasMatches() {
330
331        return m_hasMatches;
332    }
333
334    /**
335     * Constructs the filtered navigation tree.<p>
336     *
337     * @param filter the filter string
338     */
339    public void initTree(String filter) {
340
341        m_root.expandAll();
342        m_root.matchAll(filter);
343        List<NavigationNode> leaves = m_root.getLeaves();
344        m_root.markAsKeep();
345        for (NavigationNode leaf : leaves) {
346            leaf.setIsLeaf(true);
347            NavigationNode current = leaf;
348            while ((current != null) && (current != m_root) && !current.isMatch()) {
349                current = current.getParent();
350            }
351            while (current != null) {
352                current.markAsKeep();
353                current = current.getParent();
354            }
355
356        }
357        m_root.removeUnmarkedNodes();
358    }
359}