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