001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 *
019 */
020 package org.apache.directory.shared.ldap.util.tree;
021
022
023 import java.util.Enumeration;
024 import java.util.HashMap;
025 import java.util.Map;
026
027 import javax.naming.NamingException;
028
029 import org.apache.directory.shared.ldap.name.LdapDN;
030 import org.apache.directory.shared.ldap.name.Rdn;
031 import org.slf4j.Logger;
032 import org.slf4j.LoggerFactory;
033
034
035 /**
036 *
037 * The Hierarchical Container holds elements ordered by their DN.
038 * <br/>
039 * We can see them as directories, where the leaves are the files.
040 * <br/>
041 * This class is *not* thread safe
042 *
043 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
044 */
045 public class DnBranchNode<N> implements DnNode<N>
046 {
047 /** The logger for this class */
048 private static final Logger LOG = LoggerFactory.getLogger( DnBranchNode.class );
049
050 /** Stores the list of all the descendant */
051 private Map<String, DnNode<N>> children;
052
053 /** Stores the number of descendents */
054 private int size;
055
056 /**
057 * Creates a new instance of a DnBranchNode.
058 */
059 public DnBranchNode()
060 {
061 children = new HashMap<String, DnNode<N>>(3);
062 size = 0;
063 }
064
065
066 /**
067 * @see DnNode#isLeaf()
068 */
069 public boolean isLeaf()
070 {
071 return false;
072 }
073
074
075 /**
076 * Recursively adds new nodes to the element lookup tree data structure.
077 * When called it will add an element to the tree in the appropriate leaf
078 * node position based on the DN passed in as an argument.
079 *
080 * @param current The current node having an element added to it
081 * @param dn The DN associated with the added element
082 * @param index The index of the current RDN being processed
083 * @param element The associated element to add as a tree node
084 * @return The modified tree structure.
085 */
086 private DnNode<N> recursivelyAddElement( DnBranchNode<N> current, LdapDN dn, int index, N element ) throws NamingException
087 {
088 String rdnAtIndex = dn.getRdn( index ).toString();
089
090 if ( index == dn.size() - 1 )
091 {
092 if ( !current.contains( rdnAtIndex ) )
093 {
094 return current.addNode( rdnAtIndex, new DnLeafNode<N>( element ) );
095 }
096 else
097 {
098 return null;
099 }
100 }
101 else
102 {
103 DnNode<N> newNode = ((DnBranchNode<N>)current).getChild( rdnAtIndex );
104
105 if ( newNode instanceof DnLeafNode )
106 {
107 String message = "Overlapping partitions are not allowed";
108 LOG.error( message );
109 throw new NamingException( message );
110 }
111
112 if ( newNode == null )
113 {
114 newNode = new DnBranchNode<N>();
115 }
116
117 DnNode<N> child = recursivelyAddElement( (DnBranchNode<N>)newNode, dn, index + 1, element );
118
119 if ( child != null )
120 {
121 return current.addNode( rdnAtIndex, child );
122 }
123 else
124 {
125 return null;
126 }
127 }
128 }
129
130
131 /**
132 * Directly adds a new child DnNode to the current DnBranchNode.
133 *
134 * @param rdn The rdn of the child node to add
135 * @param child The child node to add
136 * @return The modified branch node after the insertion
137 */
138 public DnNode<N> addNode( String rdn, DnNode<N> child )
139 {
140 children.put( rdn, child );
141 size++;
142 return this;
143 }
144
145
146 /**
147 * Tells if the current DnBranchNode contains another node associated
148 * with an rdn.
149 *
150 * @param rdn The name we are looking for
151 * @return <code>true</code> if the tree instance contains this name
152 */
153 public boolean contains( String rdn )
154 {
155 return children.containsKey( rdn );
156 }
157
158
159 /**
160 * Get's a child using an rdn string.
161 *
162 * @param rdn the rdn to use as the node key
163 * @return the child node corresponding to the rdn.
164 */
165 public DnNode<N> getChild( String rdn )
166 {
167 if ( children.containsKey( rdn ) )
168 {
169 return children.get( rdn );
170 }
171
172 return null;
173 }
174
175
176 /**
177 * Get the parent of a given DN, if present in the tree. This parent should be a
178 * subset of the given dn.<br>
179 * For instance, if we have stored dc=acme, dc=org into the tree,
180 * the DN: ou=example, dc=acme, dc=org will have a parent, and
181 * dc=acme, dc=org will be returned.
182 * <br>For the DN ou=apache, dc=org, there is no parent, so null will be returned.
183 *
184 *
185 * @param dn the normalized distinguished name to resolve to a parent
186 * @return the parent associated with the normalized dn
187 */
188 public N getParentElement( LdapDN dn )
189 {
190 Enumeration<String> rdns = dn.getAll();
191
192 // This is synchronized so that we can't read the
193 // partitionList when it is modified.
194 synchronized ( this )
195 {
196 DnNode<N> currentNode = this;
197
198 // Iterate through all the RDN until we find the associated partition
199 while ( rdns.hasMoreElements() )
200 {
201 String rdn = rdns.nextElement();
202
203 if ( currentNode == null )
204 {
205 break;
206 }
207
208 if ( currentNode instanceof DnLeafNode )
209 {
210 return ( ( DnLeafNode<N> ) currentNode ).getElement();
211 }
212
213 DnBranchNode<N> currentBranch = ( DnBranchNode<N> ) currentNode;
214
215 if ( currentBranch.contains( rdn ) )
216 {
217 currentNode = currentBranch.getChild( rdn );
218
219 if ( currentNode instanceof DnLeafNode )
220 {
221 return ( ( DnLeafNode<N> ) currentNode ).getElement();
222 }
223 }
224 }
225 }
226
227 return null;
228 }
229
230
231 /**
232 * Tells if the DN contains a parent in the tree. This parent should be a
233 * subset of the given dn.<br>
234 * For instance, if we have stored dc=acme, dc=org into the tree,
235 * the DN: ou=example, dc=acme, dc=org will have a parent.
236 *
237 * @param dn the normalized distinguished name to resolve to a parent
238 * @return the parent associated with the normalized dn
239 */
240 public boolean hasParentElement( LdapDN dn )
241 {
242 Enumeration<Rdn> rdns = dn.getAllRdn();
243
244 // This is synchronized so that we can't read the
245 // partitionList when it is modified.
246 synchronized ( this )
247 {
248 DnNode<N> currentNode = this;
249
250 // Iterate through all the RDN until we find the associated partition
251 while ( rdns.hasMoreElements() )
252 {
253 Rdn rdn = rdns.nextElement();
254
255 if ( currentNode == null )
256 {
257 return false;
258 }
259
260 if ( currentNode instanceof DnLeafNode )
261 {
262 return true;
263 }
264
265 DnBranchNode<N> currentBranch = ( DnBranchNode<N> ) currentNode;
266
267 if ( currentBranch.contains( rdn.getNormName() ) )
268 {
269 currentNode = currentBranch.getChild( rdn.getNormName() );
270
271 if ( currentNode instanceof DnLeafNode )
272 {
273 return true;
274 }
275 }
276 }
277 }
278
279 return false;
280 }
281
282
283 /**
284 * Tells if a branchNode has some children or not
285 *
286 * @return <code>true</code> if the node has some children
287 */
288 public boolean hasChildren()
289 {
290 return children.size() != 0;
291 }
292
293
294 /**
295 * Removes an element from the tree.
296 *
297 * @param element The element to remove
298 */
299 private boolean recursivelyRemoveElement( DnBranchNode<N> currentNode, N element )
300 {
301 // It might be a leaf
302 for ( String key: currentNode.children.keySet() )
303 {
304 DnNode<N> child = currentNode.children.get( key );
305
306 if ( child instanceof DnLeafNode )
307 {
308 if ( ((DnLeafNode<N>)child).getElement().equals( element ) )
309 {
310 // found ! Remove it from the children
311 currentNode.children.remove( key );
312 currentNode.size--;
313 return true;
314 }
315 }
316 else
317 {
318 if ( recursivelyRemoveElement( (DnBranchNode<N>)child, element ) )
319 {
320 if ( ((DnBranchNode<N>)child).children.size() == 0 )
321 {
322 // If there are no more children, we can remove the node
323 currentNode.children.remove( key );
324 currentNode.size--;
325 }
326 else
327 {
328 currentNode.size--;
329 }
330
331 return true;
332 }
333 }
334 }
335
336
337 return false;
338 }
339
340
341 /**
342 *
343 * TODO add.
344 *
345 * @param dn
346 * @param element
347 * @throws NamingException
348 */
349 public void add( LdapDN dn, N element ) throws NamingException
350 {
351 recursivelyAddElement( this, dn, 0, element );
352 }
353
354
355 /**
356 * Removes an element from the tree.
357 *
358 * @param element The element to remove
359 */
360 public void remove( N element )
361 {
362 DnBranchNode<N> currentNode = this;
363
364 if ( currentNode.hasChildren() )
365 {
366 recursivelyRemoveElement( currentNode, element );
367 }
368 }
369
370
371 /**
372 * {@inheritDoc}
373 */
374 public int size()
375 {
376 return size;
377 }
378
379
380 /**
381 * @see Object#toString()
382 */
383 public String toString()
384 {
385 StringBuilder sb = new StringBuilder();
386 sb.append( "{" );
387 boolean isFirst = true;
388
389 for ( String key:children.keySet() )
390 {
391 if ( isFirst )
392 {
393 isFirst = false;
394 }
395 else
396 {
397 sb.append( ", " );
398 }
399
400 DnNode<N> child = children.get( key );
401
402 if ( child instanceof DnBranchNode )
403 {
404 sb.append( "Branch[" ).append( key ).append( "]: ").append( child );
405 }
406 else
407 {
408 sb.append( "Leaf: " ).append( "'" ).append( child ).append( "'" );
409 }
410 }
411
412 sb.append( "}" );
413 return sb.toString();
414 }
415 }