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.filter;
021    
022    
023    import java.util.HashMap;
024    import java.util.Map;
025    
026    import org.apache.directory.shared.i18n.I18n;
027    import org.apache.directory.shared.ldap.entry.BinaryValue;
028    import org.apache.directory.shared.ldap.entry.StringValue;
029    import org.apache.directory.shared.ldap.entry.Value;
030    
031    
032    /**
033     * Abstract implementation of a expression node.
034     * 
035     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
036     * @version $Rev: 928945 $
037     */
038    public abstract class AbstractExprNode implements ExprNode
039    {
040        /** The map of annotations */
041        protected Map<String, Object> annotations;
042    
043        /** The node type */
044        protected final AssertionType assertionType;
045        
046        
047        /**
048         * Creates a node by setting abstract node type.
049         * 
050         * @param assertionType The node's type
051         */
052        protected AbstractExprNode( AssertionType assertionType )
053        {
054            this.assertionType = assertionType;
055        }
056    
057    
058        /**
059         * @see ExprNode#getAssertionType()
060         * 
061         * @return the node's type
062         */
063        public AssertionType getAssertionType()
064        {
065            return assertionType;
066        }
067    
068    
069        /**
070         * Tests to see if this node is a leaf or branch node.
071         * 
072         * @return true if the node is a leaf,false otherwise
073         */
074        public abstract boolean isLeaf();
075    
076        
077        /**
078         * @see Object#equals(Object)
079         *@return <code>true</code> if both objects are equal 
080         */
081        public boolean equals( Object o )
082        {
083            // Shortcut for equals object
084            if ( this == o )
085            {
086                return true;
087            }
088            
089            if ( !( o instanceof AbstractExprNode ) )
090            {
091                return false;
092            }
093            
094            AbstractExprNode that = (AbstractExprNode)o;
095            
096            // Check the node type
097            if ( this.assertionType != that.assertionType )
098            {
099                return false;
100            }
101            
102            if ( annotations == null )
103            {
104                return that.annotations == null;
105            }
106            else if ( that.annotations == null )
107            {
108                return false;
109            }
110            
111            // Check all the annotation
112            for ( String key:annotations.keySet() )
113            {
114                if ( !that.annotations.containsKey( key ) )
115                {
116                    return false;
117                }
118                
119                Object thisAnnotation = annotations.get( key ); 
120                Object thatAnnotation = that.annotations.get( key );
121                
122                if ( thisAnnotation == null )
123                {
124                    if ( thatAnnotation != null )
125                    {
126                        return false;
127                    }
128                }
129                else
130                {
131                    if ( !thisAnnotation.equals( thatAnnotation ) )
132                    {
133                        return false;
134                    }
135                }
136            }
137            
138            return true;
139        }
140    
141    
142        /**
143         * Handles the escaping of special characters in LDAP search filter assertion values using the
144         * &lt;valueencoding&gt; rule as described in
145         * <a href="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</a>. Needed so that
146         * {@link ExprNode#printToBuffer(StringBuffer)} results in a valid filter string that can be parsed
147         * again (as a way of cloning filters).
148         *
149         * @param value Right hand side of "attrId=value" assertion occurring in an LDAP search filter.
150         * @return Escaped version of <code>value</code>
151         */
152        protected static Value<?> escapeFilterValue( Value<?> value )
153        {
154            StringBuilder sb = null;
155            String val;
156    
157            if ( value.isBinary() )
158            {
159                sb = new StringBuilder( ((BinaryValue)value).getReference().length * 3 );
160                
161                for ( byte b:((BinaryValue)value).getReference() )
162                {
163                    if ( ( b < 0x7F ) && ( b >= 0 ) )
164                    {
165                        switch ( b )
166                        {
167                            case '*' :
168                                sb.append( "\\2A" );
169                                break;
170                                
171                            case '(' :
172                                sb.append( "\\28" );
173                                break;
174                                
175                            case ')' :
176                                sb.append( "\\29" );
177                                break;
178                                
179                            case '\\' :
180                                sb.append( "\\5C" );
181                                break;
182                                
183                            case '\0' :
184                                sb.append( "\\00" );
185                                break;
186                                
187                            default :
188                                sb.append( (char)b );
189                        }
190                    }
191                    else
192                    {
193                        sb.append( '\\' );
194                        String digit = Integer.toHexString( ((byte)b) & 0x00FF );
195                        
196                        if ( digit.length() == 1 )
197                        {
198                            sb.append( '0' );
199                        }
200    
201                        sb.append( digit.toUpperCase() );
202                    }
203                }
204                
205                return new StringValue( sb.toString() );
206            }
207    
208            val = ( ( StringValue ) value ).getString();
209            
210            for ( int i = 0; i < val.length(); i++ )
211            {
212                char ch = val.charAt( i );
213                String replace = null;
214    
215                switch ( ch )
216                {
217                    case '*':
218                        replace = "\\2A";
219                        break;
220                        
221                    case '(':
222                        replace = "\\28";
223                        break;
224                        
225                    case ')':
226                        replace = "\\29";
227                        break;
228                        
229                    case '\\':
230                        replace = "\\5C";
231                        break;
232                        
233                    case '\0':
234                        replace = "\\00";
235                        break;
236                }
237                
238                if ( replace != null )
239                {
240                    if ( sb == null )
241                    {
242                        sb = new StringBuilder( val.length() * 2 );
243                        sb.append( val.substring( 0, i ) );
244                    }
245                    sb.append( replace );
246                }
247                else if ( sb != null )
248                {
249                    sb.append( ch );
250                }
251            }
252    
253            return ( sb == null ? value : new StringValue( sb.toString() ) );
254        }
255    
256    
257        /**
258         * @see Object#hashCode()
259         * @return the instance's hash code 
260         */
261        public int hashCode()
262        {
263            int h = 37;
264            
265            if ( annotations != null )
266            {
267                for ( String key:annotations.keySet() )
268                {
269                    Object value = annotations.get( key );
270                    
271                    h = h*17 + key.hashCode();
272                    h = h*17 + ( value == null ? 0 : value.hashCode() );
273                }
274            }
275            
276            return h;
277        }
278        
279    
280        /**
281         * @see org.apache.directory.shared.ldap.filter.ExprNode#get(java.lang.Object)
282         * 
283         * @return the annotation value.
284         */
285        public Object get( Object key )
286        {
287            if ( null == annotations )
288            {
289                return null;
290            }
291    
292            return annotations.get( key );
293        }
294    
295    
296        /**
297         * @see org.apache.directory.shared.ldap.filter.ExprNode#set(java.lang.Object,
298         *      java.lang.Object)
299         */
300        public void set( String key, Object value )
301        {
302            if ( null == annotations )
303            {
304                annotations = new HashMap<String, Object>( 2 );
305            }
306    
307            annotations.put( key, value );
308        }
309    
310    
311        /**
312         * Gets the annotations as a Map.
313         * 
314         * @return the annotation map.
315         */
316        protected Map<String, Object> getAnnotations()
317        {
318            return annotations;
319        }
320    
321        /**
322         * Default implementation for this method : just throw an exception.
323         * 
324         * @param buf the buffer to append to.
325         * @return The buffer in which the refinement has been appended
326         * @throws UnsupportedOperationException if this node isn't a part of a refinement.
327         */
328        public StringBuilder printRefinementToBuffer( StringBuilder buf )
329        {
330            throw new UnsupportedOperationException( I18n.err( I18n.ERR_04144 ) );
331        }
332        
333        
334        /**
335         * Clone the object
336         */
337        @Override public ExprNode clone()
338        {
339            try
340            {
341                ExprNode clone = (ExprNode)super.clone();
342                
343                if ( annotations != null )
344                {
345                    for ( String key:annotations.keySet() )
346                    {
347                        Object value = annotations.get( key );
348                        
349                        // Note : the value aren't cloned ! 
350                        ((AbstractExprNode)clone).annotations.put( key, value );
351                    }
352                }
353                
354                return clone;
355            }
356            catch ( CloneNotSupportedException cnse )
357            {
358                return null;
359            }
360        }
361    
362    
363        /**
364         * @see Object#toString()
365         */
366        public String toString()
367        {
368            if ( ( null != annotations ) && annotations.containsKey( "count" ) )
369            {
370                return ":[" + annotations.get( "count" ) + "]";
371            }
372            else 
373            {
374                return "";
375            }
376        }    
377    }