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