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}