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.shared.ldap.model.filter;
021
022
023import java.text.ParseException;
024
025import org.apache.directory.shared.i18n.I18n;
026import org.apache.directory.shared.ldap.model.entry.AttributeUtils;
027import org.apache.directory.shared.ldap.model.entry.BinaryValue;
028import org.apache.directory.shared.ldap.model.entry.Value;
029import org.apache.directory.shared.ldap.model.exception.LdapException;
030import org.apache.directory.shared.ldap.model.schema.AttributeType;
031import org.apache.directory.shared.ldap.model.schema.SchemaManager;
032import org.apache.directory.shared.util.Chars;
033import org.apache.directory.shared.util.Hex;
034import org.apache.directory.shared.util.Position;
035import org.apache.directory.shared.util.Strings;
036import org.apache.directory.shared.util.Unicode;
037
038
039/**
040 * This class parse a Ldap filter. The grammar is given in RFC 4515
041 *
042 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
043 */
044public class FilterParser
045{
046    /**
047     * Creates a filter parser implementation.
048     */
049    public FilterParser()
050    {
051    }
052
053
054    /**
055     * Parse an extensible
056     * 
057     * extensible     = ( attr [":dn"] [':' oid] ":=" assertionvalue )
058     *                  / ( [":dn"] ':' oid ":=" assertionvalue )
059     * matchingrule   = ":" oid
060     */
061    private static ExprNode parseExtensible( SchemaManager schemaManager, String attribute, String filter, Position pos ) throws LdapException, ParseException
062    {
063        ExtensibleNode node = null;
064        
065        if ( schemaManager != null )
066        {
067            AttributeType attributeType = schemaManager.getAttributeType( attribute );
068            
069            if ( attributeType != null )
070            {
071                node = new ExtensibleNode( attributeType );
072            }
073            else
074            {
075                return UndefinedNode.UNDEFINED_NODE;
076            }
077        }
078        else
079        {
080            node = new ExtensibleNode( attribute );
081        }
082
083        if ( attribute != null )
084        {
085            // First check if we have a ":dn"
086            if ( Strings.areEquals( filter, pos.start, "dn" ) )
087            {
088                // Set the dnAttributes flag and move forward in the string
089                node.setDnAttributes( true );
090                pos.start += 2;
091            }
092            else
093            {
094                // Push back the ':' 
095                pos.start--;
096            }
097
098            // Do we have a MatchingRule ?
099            if ( Strings.charAt(filter, pos.start) == ':' )
100            {
101                pos.start++;
102                int start = pos.start;
103
104                if ( Strings.charAt(filter, pos.start) == '=' )
105                {
106                    pos.start++;
107
108                    // Get the assertionValue
109                    node.setValue( parseAssertionValue( filter, pos ) );
110
111                    return node;
112                }
113                else
114                {
115                    AttributeUtils.parseAttribute( filter, pos, false );
116
117                    node.setMatchingRuleId( filter.substring( start, pos.start ) );
118
119                    if ( Strings.areEquals( filter, pos.start, ":=" ) )
120                    {
121                        pos.start += 2;
122
123                        // Get the assertionValue
124                        node.setValue( parseAssertionValue( filter, pos ) );
125
126                        return node;
127                    }
128                    else
129                    {
130                        throw new ParseException( I18n.err( I18n.ERR_04146 ), pos.start );
131                    }
132                }
133            }
134            else
135            {
136                throw new ParseException( I18n.err( I18n.ERR_04147 ), pos.start );
137            }
138        }
139        else
140        {
141            boolean oidRequested = false;
142
143            // First check if we have a ":dn"
144            if ( Strings.areEquals( filter, pos.start, ":dn" ) )
145            {
146                // Set the dnAttributes flag and move forward in the string
147                node.setDnAttributes( true );
148                pos.start += 3;
149            }
150            else
151            {
152                oidRequested = true;
153            }
154
155            // Do we have a MatchingRule ?
156            if ( Strings.charAt(filter, pos.start) == ':' )
157            {
158                pos.start++;
159                int start = pos.start;
160
161                if ( Strings.charAt(filter, pos.start) == '=' )
162                {
163                    if ( oidRequested )
164                    {
165                        throw new ParseException( I18n.err( I18n.ERR_04148 ), pos.start );
166                    }
167
168                    pos.start++;
169
170                    // Get the assertionValue
171                    node.setValue( parseAssertionValue( filter, pos ) );
172
173                    return node;
174                }
175                else
176                {
177                    AttributeUtils.parseAttribute(filter, pos, false);
178
179                    node.setMatchingRuleId( filter.substring( start, pos.start ) );
180
181                    if ( Strings.areEquals( filter, pos.start, ":=" ) )
182                    {
183                        pos.start += 2;
184
185                        // Get the assertionValue
186                        node.setValue( parseAssertionValue( filter, pos ) );
187
188                        return node;
189                    }
190                    else
191                    {
192                        throw new ParseException( I18n.err( I18n.ERR_04146 ), pos.start );
193                    }
194                }
195            }
196            else
197            {
198                throw new ParseException( I18n.err( I18n.ERR_04147 ), pos.start );
199            }
200        }
201    }
202
203
204    /**
205     * An assertion value : 
206     * assertionvalue = valueencoding
207     * valueencoding  = 0*(normal / escaped)
208     * normal         = UTF1SUBSET / UTFMB
209     * escaped        = '\' HEX HEX
210     * HEX            = '0'-'9' / 'A'-'F' / 'a'-'f'
211     * UTF1SUBSET     = %x01-27 / %x2B-5B / %x5D-7F (Everything but '\0', '*', '(', ')' and '\')
212     * UTFMB          = UTF2 / UTF3 / UTF4
213     * UTF0           = %x80-BF
214     * UTF2           = %xC2-DF UTF0
215     * UTF3           = %xE0 %xA0-BF UTF0 / %xE1-EC UTF0 UTF0 / %xED %x80-9F UTF0 / %xEE-EF UTF0 UTF0
216     * UTF4           = %xF0 %x90-BF UTF0 UTF0 / %xF1-F3 UTF0 UTF0 UTF0 / %xF4 %x80-8F UTF0 UTF0
217     * 
218     * With the specific constraints (RFC 4515):
219     *    "The <valueencoding> rule ensures that the entire filter string is a"
220     *    "valid UTF-8 string and provides that the octets that represent the"
221     *    "ASCII characters "*" (ASCII 0x2a), "(" (ASCII 0x28), ")" (ASCII"
222     *    "0x29), "\" (ASCII 0x5c), and NUL (ASCII 0x00) are represented as a"
223     *    "backslash "\" (ASCII 0x5c) followed by the two hexadecimal digits"
224     *    "representing the value of the encoded octet."
225
226     * 
227     * The incomming String is already transformed from UTF-8 to unicode, so we must assume that the 
228     * grammar we have to check is the following :
229     * 
230     * assertionvalue = valueencoding
231     * valueencoding  = 0*(normal / escaped)
232     * normal         = unicodeSubset
233     * escaped        = '\' HEX HEX
234     * HEX            = '0'-'9' / 'A'-'F' / 'a'-'f'
235     * unicodeSubset     = %x01-27 / %x2B-5B / %x5D-FFFF
236     */
237    private static Value<?> parseAssertionValue( String filter, Position pos ) throws ParseException
238    {
239        char c = Strings.charAt(filter, pos.start);
240        
241        // Create a buffer big enough to contain the value once converted
242        byte[] value = new byte[ filter.length() - pos.start];
243        int current = 0;
244
245        do
246        {
247            if ( Unicode.isUnicodeSubset(c) )
248            {
249                value[current++] = (byte)c;
250                pos.start++;
251            }
252            else if ( Strings.isCharASCII( filter, pos.start, '\\' ) )
253            {
254                // Maybe an escaped 
255                pos.start++;
256
257                // First hex
258                if ( Chars.isHex(filter, pos.start) )
259                {
260                    pos.start++;
261                }
262                else
263                {
264                    throw new ParseException( I18n.err( I18n.ERR_04149 ), pos.start );
265                }
266
267                // second hex
268                if ( Chars.isHex(filter, pos.start) )
269                {
270                    value[current++] = Hex.getHexValue(filter.charAt(pos.start - 1), filter.charAt(pos.start));
271                    pos.start++;
272                }
273                else
274                {
275                    throw new ParseException( I18n.err( I18n.ERR_04149 ), pos.start );
276                }
277            }
278            else
279            {
280                // not a valid char, so let's get out
281                break;
282            }
283        }
284        while ( ( c = Strings.charAt(filter, pos.start) ) != '\0' );
285
286        if ( current != 0 )
287        {
288            byte[] result = new byte[ current ];
289            System.arraycopy( value, 0, result, 0, current );
290            
291            return new BinaryValue( result );
292        }
293        else
294        {
295            return new BinaryValue( (byte[])null );
296        }
297    }
298
299
300    /**
301     * Parse a substring
302     */
303    private static ExprNode parseSubstring( SchemaManager schemaManager, String attribute, Value<?> initial, String filter, Position pos )
304        throws ParseException, LdapException
305    {
306        if ( Strings.isCharASCII( filter, pos.start, '*' ) )
307        {
308            // We have found a '*' : this is a substring
309            SubstringNode node = null;
310            
311            if ( schemaManager != null )
312            {
313                AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( attribute );
314                
315                if ( attributeType != null )
316                {
317                    node = new SubstringNode( schemaManager.lookupAttributeTypeRegistry( attribute ) );
318                }
319                else
320                {
321                    return null;
322                }
323            }
324            else
325            {
326                node = new SubstringNode( attribute );
327            }
328
329            if ( ( initial != null ) && !initial.isNull() )
330            {
331                // We have a substring starting with a value : val*...
332                // Set the initial value. It must be a String
333                String initialStr = initial.getString();
334                node.setInitial( initialStr );
335            }
336
337            pos.start++;
338
339            // 
340            while ( true )
341            {
342                Value<?> assertionValue = parseAssertionValue( filter, pos );
343
344                // Is there anything else but a ')' after the value ?
345                if ( Strings.isCharASCII( filter, pos.start, ')' ) )
346                {
347                    // Nope : as we have had [initial] '*' (any '*' ) *,
348                    // this is the final
349                    if ( !assertionValue.isNull() )
350                    {
351                        String finalStr = assertionValue.getString();
352                        node.setFinal( finalStr );
353                    }
354
355                    return node;
356                }
357                else if ( Strings.isCharASCII( filter, pos.start, '*' ) )
358                {
359                    // We have a '*' : it's an any
360                    // If the value is empty, that means we have more than 
361                    // one consecutive '*' : do nothing in this case.
362                    if ( !assertionValue.isNull() )
363                    {
364                        String anyStr = assertionValue.getString();
365                        node.addAny( anyStr );
366                    }
367
368                    pos.start++;
369                }
370                else
371                {
372                    // This is an error
373                    throw new ParseException( I18n.err( I18n.ERR_04150 ), pos.start );
374                }
375            }
376        }
377        else
378        {
379            // This is an error
380            throw new ParseException( I18n.err( I18n.ERR_04150 ), pos.start );
381        }
382    }
383
384
385    /**
386     * Here is the grammar to parse :
387     * 
388     * simple    ::= '=' assertionValue
389     * present   ::= '=' '*'
390     * substring ::= '=' [initial] any [final]
391     * initial   ::= assertionValue
392     * any       ::= '*' ( assertionValue '*')*
393     * 
394     * As we can see, there is an ambiguity in the grammar : attr=* can be
395     * seen as a present or as a substring. As stated in the RFC :
396     * 
397     * "Note that although both the <substring> and <present> productions in"
398     * "the grammar above can produce the "attr=*" construct, this construct"
399     * "is used only to denote a presence filter." (RFC 4515, 3)
400     * 
401     * We have also to consider the difference between a substring and the
402     * equality node : this last node does not contain a '*'
403     *
404     * @param attributeType
405     * @param filter
406     * @param pos
407     * @return
408     */
409    @SuppressWarnings({ "rawtypes", "unchecked" })
410    private static ExprNode parsePresenceEqOrSubstring( SchemaManager schemaManager, String attribute, String filter, Position pos )
411        throws ParseException, LdapException
412    {
413        if ( Strings.isCharASCII( filter, pos.start, '*' ) )
414        {
415            // To be a present node, the next char should be a ')'
416            pos.start++;
417
418            if ( Strings.isCharASCII( filter, pos.start, ')' ) )
419            {
420                // This is a present node
421                if ( schemaManager != null )
422                {
423                    AttributeType attributeType = schemaManager.getAttributeType( attribute );
424                    
425                    if ( attributeType != null )
426                    {
427                        return new PresenceNode( attributeType );
428                    }
429                    else
430                    {
431                        return null;
432                    }
433                }
434                else
435                {
436                    return new PresenceNode( attribute );
437                }
438            }
439            else
440            {
441                // Definitively a substring with no initial or an error
442                // Push back the '*' on the string
443                pos.start--;
444                return parseSubstring( schemaManager, attribute, null, filter, pos );
445            }
446        }
447        else if ( Strings.isCharASCII( filter, pos.start, ')' ) )
448        {
449            // An empty equality Node
450            if ( schemaManager != null )
451            {
452                AttributeType attributeType = schemaManager.getAttributeType( attribute );
453                
454                if ( attributeType != null )
455                {
456                    return new EqualityNode( attributeType, new BinaryValue( (byte[])null ) );
457                }
458                
459                else
460                {
461                    return null;
462                }
463            }
464            else
465            {
466                return new EqualityNode( attribute, new BinaryValue( (byte[])null ) );
467            }
468        }
469        else
470        {
471            // A substring or an equality node
472            Value<?> value = parseAssertionValue( filter, pos );
473
474            // Is there anything else but a ')' after the value ?
475            if ( Strings.isCharASCII( filter, pos.start, ')' ) )
476            {
477                // This is an equality node
478                if ( schemaManager != null )
479                {
480                    AttributeType attributeType = schemaManager.getAttributeType( attribute );
481                    
482                    if ( attributeType != null )
483                    {
484                        return new EqualityNode( attributeType, value );
485                    }
486                    else
487                    {
488                        return null;
489                    }
490                }
491                else
492                {
493                    return new EqualityNode( attribute, value );
494                }
495            }
496
497            return parseSubstring( schemaManager, attribute, value, filter, pos );
498        }
499    }
500
501
502    /**
503     * Parse the following grammar :
504     * item           = simple / present / substring / extensible
505     * simple         = attr filtertype assertionvalue
506     * filtertype     = '=' / '~=' / '>=' / '<='
507     * present        = attr '=' '*'
508     * substring      = attr '=' [initial] any [final]
509     * extensible     = ( attr [":dn"] [':' oid] ":=" assertionvalue )
510     *                  / ( [":dn"] ':' oid ":=" assertionvalue )
511     * matchingrule   = ":" oid
512     *                  
513     * An item starts with an attribute or a colon.
514     */
515    @SuppressWarnings({ "rawtypes", "unchecked" })
516    private static ExprNode parseItem( SchemaManager schemaManager, String filter, Position pos, char c ) 
517        throws ParseException, LdapException
518    {
519        String attribute = null;
520
521        if ( c == '\0' )
522        {
523            throw new ParseException( I18n.err( I18n.ERR_04151 ), pos.start );
524        }
525
526        if ( c == ':' )
527        {
528            // If we have a colon, then the item is an extensible one
529            return parseExtensible( schemaManager, null, filter, pos );
530        }
531        else
532        {
533            // We must have an attribute
534            attribute = AttributeUtils.parseAttribute( filter, pos, true );
535            
536            // Now, we may have a present, substring, simple or an extensible
537            c = Strings.charAt(filter, pos.start);
538
539            switch ( c )
540            {
541                case '=':
542                    // It can be a presence, an equal or a substring
543                    pos.start++;
544                    return parsePresenceEqOrSubstring( schemaManager, attribute, filter, pos );
545
546                case '~':
547                    // Approximate node
548                    pos.start++;
549
550                    // Check that we have a '='
551                    if ( !Strings.isCharASCII( filter, pos.start, '=' ) )
552                    {
553                        throw new ParseException( I18n.err( I18n.ERR_04152 ), pos.start );
554                    }
555
556                    pos.start++;
557
558                    // Parse the value and create the node
559                    if ( schemaManager == null )
560                    {
561                        return new ApproximateNode( attribute, parseAssertionValue( filter, pos ) );
562                    }
563                    else
564                    {
565                        AttributeType attributeType = schemaManager.getAttributeType( attribute );
566                        
567                        if ( attributeType != null )
568                        {
569                            return new ApproximateNode( attributeType, parseAssertionValue( filter, pos ) );
570                        }
571                        else
572                        {
573                            return UndefinedNode.UNDEFINED_NODE;
574                        }
575                    }
576
577                case '>':
578                    // Greater or equal node
579                    pos.start++;
580
581                    // Check that we have a '='
582                    if ( !Strings.isCharASCII( filter, pos.start, '=' ) )
583                    {
584                        throw new ParseException( I18n.err( I18n.ERR_04152 ), pos.start );
585                    }
586
587                    pos.start++;
588
589                    // Parse the value and create the node
590                    if ( schemaManager == null )
591                    {
592                        return new GreaterEqNode( attribute, parseAssertionValue( filter, pos ) );
593                    }
594                    else
595                    {
596                        AttributeType attributeType = schemaManager.getAttributeType( attribute );
597                        
598                        if ( attributeType != null )
599                        {
600                            return new GreaterEqNode( attributeType, parseAssertionValue( filter, pos ) );
601                        }
602                        else
603                        {
604                            return UndefinedNode.UNDEFINED_NODE;
605                        }
606                    }
607
608                case '<':
609                    // Less or equal node
610                    pos.start++;
611
612                    // Check that we have a '='
613                    if ( !Strings.isCharASCII( filter, pos.start, '=' ) )
614                    {
615                        throw new ParseException( I18n.err( I18n.ERR_04152 ), pos.start );
616                    }
617
618                    pos.start++;
619
620                    // Parse the value and create the node
621                    if ( schemaManager == null )
622                    {
623                        return new LessEqNode( attribute, parseAssertionValue( filter, pos ) );
624                    }
625                    else
626                    {
627                        AttributeType attributeType = schemaManager.getAttributeType( attribute );
628                        
629                        if ( attributeType != null )
630                        {
631                            return new LessEqNode( attributeType, parseAssertionValue( filter, pos ) );
632                        }
633                        else
634                        {
635                            return UndefinedNode.UNDEFINED_NODE;
636                        }
637                    }
638
639                case ':':
640                    // An extensible node
641                    pos.start++;
642                    return parseExtensible( schemaManager, attribute, filter, pos );
643
644                default:
645                    // This is an error
646                    throw new ParseException( I18n.err( I18n.ERR_04153 ), pos.start );
647            }
648        }
649    }
650
651
652    /**
653     * Parse AND, OR and NOT nodes :
654     * 
655     * and            = '&' filterlist
656     * or             = '|' filterlist
657     * not            = '!' filter
658     * filterlist     = 1*filter
659     * 
660     * @return
661     */
662    private static ExprNode parseBranchNode( SchemaManager schemaManager, ExprNode node, String filter, Position pos ) 
663        throws ParseException, LdapException
664    {
665        BranchNode branchNode = ( BranchNode ) node;
666        int nbChildren = 0;
667
668        // We must have at least one filter
669        ExprNode child = parseFilterInternal( schemaManager, filter, pos );
670        
671        if ( child != UndefinedNode.UNDEFINED_NODE )
672        {
673            // Add the child to the node children
674            branchNode.addNode( child );
675            
676            if ( branchNode instanceof NotNode )
677            {
678                return node;
679            }
680            
681            nbChildren++;
682        }
683        else if ( node instanceof AndNode )
684        {
685            return UndefinedNode.UNDEFINED_NODE;
686        }
687
688        // Now, iterate recusively though all the remaining filters, if any
689        while ( ( child = parseFilterInternal( schemaManager, filter, pos ) ) != UndefinedNode.UNDEFINED_NODE )
690        {
691            // Add the child to the node children if not null
692            if ( child != null )
693            {
694                branchNode.addNode( child );
695                nbChildren++;
696            }
697            else if ( node instanceof AndNode )
698            {
699                return UndefinedNode.UNDEFINED_NODE;
700            }
701        }
702
703        if ( nbChildren > 0 )
704        {
705            return node;
706        }
707        else
708        {
709            return UndefinedNode.UNDEFINED_NODE;
710        }
711    }
712
713
714    /**
715     * filtercomp     = and / or / not / item
716     * and            = '&' filterlist
717     * or             = '|' filterlist
718     * not            = '!' filter
719     * item           = simple / present / substring / extensible
720     * simple         = attr filtertype assertionvalue
721     * present        = attr EQUALS ASTERISK
722     * substring      = attr EQUALS [initial] any [final]
723     * extensible     = ( attr [dnattrs]
724     *                    [matchingrule] COLON EQUALS assertionvalue )
725     *                    / ( [dnattrs]
726     *                         matchingrule COLON EQUALS assertionvalue )
727     */
728    private static ExprNode parseFilterComp( SchemaManager schemaManager, String filter, Position pos ) 
729        throws ParseException, LdapException
730    {
731        ExprNode node = null;
732
733        if ( pos.start == pos.length )
734        {
735            throw new ParseException( I18n.err( I18n.ERR_04154 ), pos.start );
736        }
737
738        char c = Strings.charAt(filter, pos.start);
739
740        switch ( c )
741        {
742            case '&':
743                // This is a AND node
744                pos.start++;
745                node = new AndNode();
746                node = parseBranchNode( schemaManager, node, filter, pos );
747                break;
748
749            case '|':
750                // This is an OR node
751                pos.start++;
752                node = new OrNode();
753                node = parseBranchNode( schemaManager, node, filter, pos );
754                break;
755
756            case '!':
757                // This is a NOT node
758                pos.start++;
759                node = new NotNode();
760                node = parseBranchNode( schemaManager, node, filter, pos );
761                break;
762
763            default:
764                // This is an item
765                node = parseItem( schemaManager, filter, pos, c );
766                break;
767
768        }
769
770        return node;
771    }
772
773
774    /**
775     * Pasre the grammar rule :
776     * filter ::= '(' filterComp ')'
777     */
778    private static ExprNode parseFilterInternal( SchemaManager schemaManager, String filter, Position pos )
779        throws ParseException, LdapException
780    {
781        // Check for the left '('
782        if ( !Strings.isCharASCII( filter, pos.start, '(' ) )
783        {
784            // No more node, get out
785            if ( ( pos.start == 0 ) && ( pos.length != 0 ) )
786            {
787                throw new ParseException( I18n.err( I18n.ERR_04155 ), 0 );
788            }
789            else
790            {
791                return UndefinedNode.UNDEFINED_NODE;
792            }
793        }
794
795        pos.start++;
796
797        // parse the filter component
798        ExprNode node = parseFilterComp( schemaManager, filter, pos );
799
800        if ( node == UndefinedNode.UNDEFINED_NODE )
801        {
802            return UndefinedNode.UNDEFINED_NODE;
803        }
804
805        // Check that we have a right ')'
806        if ( !Strings.isCharASCII( filter, pos.start, ')' ) )
807        {
808            throw new ParseException( I18n.err( I18n.ERR_04157 ), pos.start );
809        }
810
811        pos.start++;
812
813        return node;
814    }
815
816
817    /**
818     * @see FilterParser#parse(String)
819     */
820    public static ExprNode parse( String filter ) throws ParseException
821    {
822        return parse( null, filter );
823    }
824    
825    
826    /**
827     * @see FilterParser#parse(String)
828     */
829    public static ExprNode parse( SchemaManager schemaManager, String filter ) throws ParseException
830    {
831        // The filter must not be null. This is a defensive test
832        if ( Strings.isEmpty(filter) )
833        {
834            throw new ParseException( I18n.err( I18n.ERR_04158 ), 0 );
835        }
836
837        Position pos = new Position();
838        pos.start = 0;
839        pos.end = 0;
840        pos.length = filter.length();
841
842        try
843        {
844            return parseFilterInternal( schemaManager, filter, pos );
845        }
846        catch ( LdapException le )
847        {
848            throw new ParseException( le.getMessage(), pos.start );
849        }
850    }
851
852
853    /**
854     * @see FilterParser#parse(String)
855     */
856    public static ExprNode parse( SchemaManager schemaManager, String filter, Position pos ) throws ParseException
857    {
858        // The filter must not be null. This is a defensive test
859        if ( Strings.isEmpty(filter) )
860        {
861            throw new ParseException( I18n.err( I18n.ERR_04158 ), 0 );
862        }
863
864        pos.start = 0;
865        pos.end = 0;
866        pos.length = filter.length();
867
868        try
869        {
870            return parseFilterInternal( schemaManager, filter, pos );
871        }
872        catch ( LdapException le )
873        {
874            throw new ParseException( le.getMessage(), pos.start );
875        }
876    }
877}