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    }