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;
021    
022    
023    import java.io.ByteArrayOutputStream;
024    import java.io.UnsupportedEncodingException;
025    import java.net.URI;
026    import java.text.ParseException;
027    import java.util.ArrayList;
028    import java.util.HashSet;
029    import java.util.List;
030    import java.util.Set;
031    
032    import javax.naming.InvalidNameException;
033    
034    import org.apache.directory.shared.asn1.codec.binary.Hex;
035    import org.apache.directory.shared.i18n.I18n;
036    import org.apache.directory.shared.ldap.codec.util.HttpClientError;
037    import org.apache.directory.shared.ldap.codec.util.LdapURLEncodingException;
038    import org.apache.directory.shared.ldap.codec.util.URIException;
039    import org.apache.directory.shared.ldap.codec.util.UrlDecoderException;
040    import org.apache.directory.shared.ldap.filter.FilterParser;
041    import org.apache.directory.shared.ldap.filter.SearchScope;
042    import org.apache.directory.shared.ldap.name.DN;
043    
044    
045    /**
046     * Decodes a LdapUrl, and checks that it complies with
047     * the RFC 2255. The grammar is the following :
048     * ldapurl    = scheme "://" [hostport] ["/"
049     *                   [dn ["?" [attributes] ["?" [scope]
050     *                   ["?" [filter] ["?" extensions]]]]]]
051     * scheme     = "ldap"
052     * attributes = attrdesc *("," attrdesc)
053     * scope      = "base" / "one" / "sub"
054     * dn         = DN
055     * hostport   = hostport from Section 5 of RFC 1738
056     * attrdesc   = AttributeDescription from Section 4.1.5 of RFC 2251
057     * filter     = filter from Section 4 of RFC 2254
058     * extensions = extension *("," extension)
059     * extension  = ["!"] extype ["=" exvalue]
060     * extype     = token / xtoken
061     * exvalue    = LDAPString
062     * token      = oid from section 4.1 of RFC 2252
063     * xtoken     = ("X-" / "x-") token
064     * 
065     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
066     * @version $Rev: 919765 $, $Date: 2010-03-06 14:44:54 +0100 (Sam, 06 mar 2010) $, 
067     */
068    public class LdapURL
069    {
070    
071        // ~ Static fields/initializers
072        // -----------------------------------------------------------------
073    
074        /** The constant for "ldaps://" scheme. */
075        public static final String LDAPS_SCHEME = "ldaps://";
076    
077        /** The constant for "ldap://" scheme. */
078        public static final String LDAP_SCHEME = "ldap://";
079    
080        /** A null LdapURL */
081        public static final LdapURL EMPTY_URL = new LdapURL();
082    
083        // ~ Instance fields
084        // ----------------------------------------------------------------------------
085    
086        /** The scheme */
087        private String scheme;
088    
089        /** The host */
090        private String host;
091    
092        /** The port */
093        private int port;
094    
095        /** The DN */
096        private DN dn;
097    
098        /** The attributes */
099        private List<String> attributes;
100    
101        /** The scope */
102        private SearchScope scope;
103    
104        /** The filter as a string */
105        private String filter;
106    
107        /** The extensions. */
108        private List<Extension> extensionList;
109    
110        /** Stores the LdapURL as a String */
111        private String string;
112    
113        /** Stores the LdapURL as a byte array */
114        private byte[] bytes;
115    
116        /** modal parameter that forces explicit scope rendering in toString */
117        private boolean forceScopeRendering;
118    
119    
120        // ~ Constructors
121        // -------------------------------------------------------------------------------
122    
123        /**
124         * Construct an empty LdapURL
125         */
126        public LdapURL()
127        {
128            scheme = LDAP_SCHEME;
129            host = null;
130            port = -1;
131            dn = null;
132            attributes = new ArrayList<String>();
133            scope = SearchScope.OBJECT;
134            filter = null;
135            extensionList = new ArrayList<Extension>( 2 );
136        }
137    
138    
139        /**
140         * Parse a LdapURL
141         * @param chars The chars containing the URL
142         * @throws LdapURLEncodingException If the URL is invalid
143         */
144        public void parse( char[] chars ) throws LdapURLEncodingException
145        {
146            scheme = LDAP_SCHEME;
147            host = null;
148            port = -1;
149            dn = null;
150            attributes = new ArrayList<String>();
151            scope = SearchScope.OBJECT;
152            filter = null;
153            extensionList = new ArrayList<Extension>( 2 );
154    
155            if ( ( chars == null ) || ( chars.length == 0 ) )
156            {
157                host = "";
158                return;
159            }
160    
161            // ldapurl = scheme "://" [hostport] ["/"
162            // [dn ["?" [attributes] ["?" [scope]
163            // ["?" [filter] ["?" extensions]]]]]]
164            // scheme = "ldap"
165    
166            int pos = 0;
167    
168            // The scheme
169            if ( ( ( pos = StringTools.areEquals( chars, 0, LDAP_SCHEME ) ) == StringTools.NOT_EQUAL )
170                && ( ( pos = StringTools.areEquals( chars, 0, LDAPS_SCHEME ) ) == StringTools.NOT_EQUAL ) )
171            {
172                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04398 ) );
173            }
174            else
175            {
176                scheme = new String( chars, 0, pos );
177            }
178    
179            // The hostport
180            if ( ( pos = parseHostPort( chars, pos ) ) == -1 )
181            {
182                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04399 ) );
183            }
184    
185            if ( pos == chars.length )
186            {
187                return;
188            }
189    
190            // An optional '/'
191            if ( !StringTools.isCharASCII( chars, pos, '/' ) )
192            {
193                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04400, pos, chars[pos] ) );
194            }
195    
196            pos++;
197    
198            if ( pos == chars.length )
199            {
200                return;
201            }
202    
203            // An optional DN
204            if ( ( pos = parseDN( chars, pos ) ) == -1 )
205            {
206                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04401 ) );
207            }
208    
209            if ( pos == chars.length )
210            {
211                return;
212            }
213    
214            // Optionals attributes
215            if ( !StringTools.isCharASCII( chars, pos, '?' ) )
216            {
217                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04402, pos, chars[pos] ) );
218            }
219    
220            pos++;
221    
222            if ( ( pos = parseAttributes( chars, pos ) ) == -1 )
223            {
224                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04403 ) );
225            }
226    
227            if ( pos == chars.length )
228            {
229                return;
230            }
231    
232            // Optional scope
233            if ( !StringTools.isCharASCII( chars, pos, '?' ) )
234            {
235                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04402, pos, chars[pos] ) );
236            }
237    
238            pos++;
239    
240            if ( ( pos = parseScope( chars, pos ) ) == -1 )
241            {
242                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04404 ) );
243            }
244    
245            if ( pos == chars.length )
246            {
247                return;
248            }
249    
250            // Optional filter
251            if ( !StringTools.isCharASCII( chars, pos, '?' ) )
252            {
253                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04402, pos, chars[pos] ) );
254            }
255    
256            pos++;
257    
258            if ( pos == chars.length )
259            {
260                return;
261            }
262    
263            if ( ( pos = parseFilter( chars, pos ) ) == -1 )
264            {
265                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04405 ) );
266            }
267    
268            if ( pos == chars.length )
269            {
270                return;
271            }
272    
273            // Optional extensions
274            if ( !StringTools.isCharASCII( chars, pos, '?' ) )
275            {
276                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04402, pos, chars[pos] ) );
277            }
278    
279            pos++;
280    
281            if ( ( pos = parseExtensions( chars, pos ) ) == -1 )
282            {
283                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04406 ) );
284            }
285    
286            if ( pos == chars.length )
287            {
288                return;
289            }
290            else
291            {
292                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04407 ) );
293            }
294        }
295    
296    
297        /**
298         * Create a new LdapURL from a String after having parsed it.
299         * 
300         * @param string TheString that contains the LDAPURL
301         * @throws LdapURLEncodingException If the String does not comply with RFC 2255
302         */
303        public LdapURL( String string ) throws LdapURLEncodingException
304        {
305            if ( string == null )
306            {
307                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04408 ) );
308            }
309    
310            try
311            {
312                bytes = string.getBytes( "UTF-8" );
313                this.string = string;
314                parse( string.toCharArray() );
315            }
316            catch ( UnsupportedEncodingException uee )
317            {
318                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04409, string ) );
319            }
320        }
321    
322    
323        /**
324         * Create a new LdapURL after having parsed it.
325         * 
326         * @param bytes The byte buffer that contains the LDAPURL
327         * @throws LdapURLEncodingException If the byte array does not comply with RFC 2255
328         */
329        public LdapURL( byte[] bytes ) throws LdapURLEncodingException
330        {
331            if ( ( bytes == null ) || ( bytes.length == 0 ) )
332            {
333                throw new LdapURLEncodingException( I18n.err( I18n.ERR_04410 ) );
334            }
335    
336            string = StringTools.utf8ToString( bytes );
337    
338            this.bytes = new byte[bytes.length];
339            System.arraycopy( bytes, 0, this.bytes, 0, bytes.length );
340    
341            parse( string.toCharArray() );
342        }
343    
344    
345        // ~ Methods
346        // ------------------------------------------------------------------------------------
347    
348        /**
349         * Parse this rule : <br>
350         * <p>
351         * &lt;host&gt; ::= &lt;hostname&gt; ':' &lt;hostnumber&gt;<br>
352         * &lt;hostname&gt; ::= *[ &lt;domainlabel&gt; "." ] &lt;toplabel&gt;<br>
353         * &lt;domainlabel&gt; ::= &lt;alphadigit&gt; | &lt;alphadigit&gt; *[
354         * &lt;alphadigit&gt; | "-" ] &lt;alphadigit&gt;<br>
355         * &lt;toplabel&gt; ::= &lt;alpha&gt; | &lt;alpha&gt; *[ &lt;alphadigit&gt; |
356         * "-" ] &lt;alphadigit&gt;<br>
357         * &lt;hostnumber&gt; ::= &lt;digits&gt; "." &lt;digits&gt; "."
358         * &lt;digits&gt; "." &lt;digits&gt;
359         * </p>
360         * 
361         * @param chars The buffer to parse
362         * @param pos The current position in the byte buffer
363         * @return The new position in the byte buffer, or -1 if the rule does not
364         *         apply to the byte buffer TODO check that the topLabel is valid
365         *         (it must start with an alpha)
366         */
367        private int parseHost( char[] chars, int pos )
368        {
369    
370            int start = pos;
371            boolean hadDot = false;
372            boolean hadMinus = false;
373            boolean isHostNumber = true;
374            boolean invalidIp = false;
375            int nbDots = 0;
376            int[] ipElem = new int[4];
377    
378            // The host will be followed by a '/' or a ':', or by nothing if it's
379            // the end.
380            // We will search the end of the host part, and we will check some
381            // elements.
382            if ( StringTools.isCharASCII( chars, pos, '-' ) )
383            {
384    
385                // We can't have a '-' on first position
386                return -1;
387            }
388    
389            while ( ( pos < chars.length ) && ( chars[pos] != ':' ) && ( chars[pos] != '/' ) )
390            {
391    
392                if ( StringTools.isCharASCII( chars, pos, '.' ) )
393                {
394    
395                    if ( ( hadMinus ) || ( hadDot ) )
396                    {
397    
398                        // We already had a '.' just before : this is not allowed.
399                        // Or we had a '-' before a '.' : ths is not allowed either.
400                        return -1;
401                    }
402    
403                    // Let's check the string we had before the dot.
404                    if ( isHostNumber )
405                    {
406    
407                        if ( nbDots < 4 )
408                        {
409    
410                            // We had only digits. It may be an IP adress? Check it
411                            if ( ipElem[nbDots] > 65535 )
412                            {
413                                invalidIp = true;
414                            }
415                        }
416                    }
417    
418                    hadDot = true;
419                    nbDots++;
420                    pos++;
421                    continue;
422                }
423                else
424                {
425    
426                    if ( hadDot && StringTools.isCharASCII( chars, pos, '-' ) )
427                    {
428    
429                        // We can't have a '-' just after a '.'
430                        return -1;
431                    }
432    
433                    hadDot = false;
434                }
435    
436                if ( StringTools.isDigit( chars, pos ) )
437                {
438    
439                    if ( isHostNumber && ( nbDots < 4 ) )
440                    {
441                        ipElem[nbDots] = ( ipElem[nbDots] * 10 ) + ( chars[pos] - '0' );
442    
443                        if ( ipElem[nbDots] > 65535 )
444                        {
445                            invalidIp = true;
446                        }
447                    }
448    
449                    hadMinus = false;
450                }
451                else if ( StringTools.isAlphaDigitMinus( chars, pos ) )
452                {
453                    isHostNumber = false;
454    
455                    if ( StringTools.isCharASCII( chars, pos, '-' ) )
456                    {
457                        hadMinus = true;
458                    }
459                    else
460                    {
461                        hadMinus = false;
462                    }
463                }
464                else
465                {
466                    return -1;
467                }
468    
469                pos++;
470            }
471    
472            if ( start == pos )
473            {
474    
475                // An empty host is valid
476                return pos;
477            }
478    
479            // Checks the hostNumber
480            if ( isHostNumber )
481            {
482    
483                // As this is a host number, we must have 3 dots.
484                if ( nbDots != 3 )
485                {
486                    return -1;
487                }
488    
489                if ( invalidIp )
490                {
491                    return -1;
492                }
493            }
494    
495            // Check if we have a '.' or a '-' in last position
496            if ( hadDot || hadMinus )
497            {
498                return -1;
499            }
500    
501            host = new String( chars, start, pos - start );
502    
503            return pos;
504        }
505    
506    
507        /**
508         * Parse this rule : <br>
509         * <p>
510         * &lt;port&gt; ::= &lt;digits&gt;<br>
511         * &lt;digits&gt; ::= &lt;digit&gt; &lt;digits-or-null&gt;<br>
512         * &lt;digits-or-null&gt; ::= &lt;digit&gt; &lt;digits-or-null&gt; | e<br>
513         * &lt;digit&gt; ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
514         * </p>
515         * The port must be between 0 and 65535.
516         * 
517         * @param chars The buffer to parse
518         * @param pos The current position in the byte buffer
519         * @return The new position in the byte buffer, or -1 if the rule does not
520         *         apply to the byte buffer
521         */
522        private int parsePort( char[] chars, int pos )
523        {
524    
525            if ( !StringTools.isDigit( chars, pos ) )
526            {
527                return -1;
528            }
529    
530            port = chars[pos] - '0';
531    
532            pos++;
533    
534            while ( StringTools.isDigit( chars, pos ) )
535            {
536                port = ( port * 10 ) + ( chars[pos] - '0' );
537    
538                if ( port > 65535 )
539                {
540                    return -1;
541                }
542    
543                pos++;
544            }
545    
546            return pos;
547        }
548    
549    
550        /**
551         * Parse this rule : <br>
552         * <p>
553         * &lt;hostport&gt; ::= &lt;host&gt; ':' &lt;port&gt;
554         * </p>
555         * 
556         * @param chars The char array to parse
557         * @param pos The current position in the byte buffer
558         * @return The new position in the byte buffer, or -1 if the rule does not
559         *         apply to the byte buffer
560         */
561        private int parseHostPort( char[] chars, int pos )
562        {
563            int hostPos = pos;
564    
565            if ( ( pos = parseHost( chars, pos ) ) == -1 )
566            {
567                return -1;
568            }
569    
570            // We may have a port.
571            if ( StringTools.isCharASCII( chars, pos, ':' ) )
572            {
573                if ( pos == hostPos )
574                {
575                    // We should not have a port if we have no host
576                    return -1;
577                }
578    
579                pos++;
580            }
581            else
582            {
583                return pos;
584            }
585    
586            // As we have a ':', we must have a valid port (between 0 and 65535).
587            if ( ( pos = parsePort( chars, pos ) ) == -1 )
588            {
589                return -1;
590            }
591    
592            return pos;
593        }
594    
595    
596        /**
597         * From commons-httpclients. Converts the byte array of HTTP content
598         * characters to a string. If the specified charset is not supported,
599         * default system encoding is used.
600         * 
601         * @param data the byte array to be encoded
602         * @param offset the index of the first byte to encode
603         * @param length the number of bytes to encode
604         * @param charset the desired character encoding
605         * @return The result of the conversion.
606         * @since 3.0
607         */
608        public static String getString( final byte[] data, int offset, int length, String charset )
609        {
610            if ( data == null )
611            {
612                throw new IllegalArgumentException( I18n.err( I18n.ERR_04411 ) );
613            }
614    
615            if ( charset == null || charset.length() == 0 )
616            {
617                throw new IllegalArgumentException( I18n.err( I18n.ERR_04412 ) );
618            }
619    
620            try
621            {
622                return new String( data, offset, length, charset );
623            }
624            catch ( UnsupportedEncodingException e )
625            {
626                return new String( data, offset, length );
627            }
628        }
629    
630    
631        /**
632         * From commons-httpclients. Converts the byte array of HTTP content
633         * characters to a string. If the specified charset is not supported,
634         * default system encoding is used.
635         * 
636         * @param data the byte array to be encoded
637         * @param charset the desired character encoding
638         * @return The result of the conversion.
639         * @since 3.0
640         */
641        public static String getString( final byte[] data, String charset )
642        {
643            return getString( data, 0, data.length, charset );
644        }
645    
646    
647        /**
648         * Converts the specified string to byte array of ASCII characters.
649         * 
650         * @param data the string to be encoded
651         * @return The string as a byte array.
652         * @since 3.0
653         */
654        public static byte[] getAsciiBytes( final String data )
655        {
656    
657            if ( data == null )
658            {
659                throw new IllegalArgumentException( I18n.err( I18n.ERR_04411 ) );
660            }
661    
662            try
663            {
664                return data.getBytes( "US-ASCII" );
665            }
666            catch ( UnsupportedEncodingException e )
667            {
668                throw new HttpClientError( I18n.err( I18n.ERR_04413 ) );
669            }
670        }
671    
672    
673        /**
674         * From commons-codec. Decodes an array of URL safe 7-bit characters into an
675         * array of original bytes. Escaped characters are converted back to their
676         * original representation.
677         * 
678         * @param bytes array of URL safe characters
679         * @return array of original bytes
680         * @throws UrlDecoderException Thrown if URL decoding is unsuccessful
681         */
682        private static final byte[] decodeUrl( byte[] bytes ) throws UrlDecoderException
683        {
684            if ( bytes == null )
685            {
686                return StringTools.EMPTY_BYTES;
687            }
688    
689            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
690    
691            for ( int i = 0; i < bytes.length; i++ )
692            {
693                int b = bytes[i];
694    
695                if ( b == '%' )
696                {
697                    try
698                    {
699                        int u = Character.digit( ( char ) bytes[++i], 16 );
700                        int l = Character.digit( ( char ) bytes[++i], 16 );
701    
702                        if ( u == -1 || l == -1 )
703                        {
704                            throw new UrlDecoderException( I18n.err( I18n.ERR_04414 ) );
705                        }
706    
707                        buffer.write( ( char ) ( ( u << 4 ) + l ) );
708                    }
709                    catch ( ArrayIndexOutOfBoundsException e )
710                    {
711                        throw new UrlDecoderException( I18n.err( I18n.ERR_04414 ) );
712                    }
713                }
714                else
715                {
716                    buffer.write( b );
717                }
718            }
719    
720            return buffer.toByteArray();
721        }
722    
723    
724        /**
725         * From commons-httpclients. Unescape and decode a given string regarded as
726         * an escaped string with the default protocol charset.
727         * 
728         * @param escaped a string
729         * @return the unescaped string
730         * @throws URIException if the string cannot be decoded (invalid)
731         * @see URI#getDefaultProtocolCharset
732         */
733        private static String decode( String escaped ) throws URIException
734        {
735            try
736            {
737                byte[] rawdata = decodeUrl( getAsciiBytes( escaped ) );
738                return getString( rawdata, "UTF-8" );
739            }
740            catch ( UrlDecoderException e )
741            {
742                throw new URIException( e.getMessage() );
743            }
744        }
745    
746    
747        /**
748         * Parse a string and check that it complies with RFC 2253. Here, we will
749         * just call the DN parser to do the job.
750         * 
751         * @param chars The char array to be checked
752         * @param pos the starting position
753         * @return -1 if the char array does not contains a DN
754         */
755        private int parseDN( char[] chars, int pos )
756        {
757    
758            int end = pos;
759    
760            for ( int i = pos; ( i < chars.length ) && ( chars[i] != '?' ); i++ )
761            {
762                end++;
763            }
764    
765            try
766            {
767                dn = new DN( decode( new String( chars, pos, end - pos ) ) );
768            }
769            catch ( URIException ue )
770            {
771                return -1;
772            }
773            catch ( InvalidNameException de )
774            {
775                return -1;
776            }
777    
778            return end;
779        }
780    
781    
782        /**
783         * Parse the attributes part
784         * 
785         * @param chars The char array to be checked
786         * @param pos the starting position
787         * @return -1 if the char array does not contains attributes
788         */
789        private int parseAttributes( char[] chars, int pos )
790        {
791    
792            int start = pos;
793            int end = pos;
794            Set<String> hAttributes = new HashSet<String>();
795            boolean hadComma = false;
796    
797            try
798            {
799    
800                for ( int i = pos; ( i < chars.length ) && ( chars[i] != '?' ); i++ )
801                {
802    
803                    if ( StringTools.isCharASCII( chars, i, ',' ) )
804                    {
805                        hadComma = true;
806    
807                        if ( ( end - start ) == 0 )
808                        {
809    
810                            // An attributes must not be null
811                            return -1;
812                        }
813                        else
814                        {
815                            String attribute = null;
816    
817                            // get the attribute. It must not be blank
818                            attribute = new String( chars, start, end - start ).trim();
819    
820                            if ( attribute.length() == 0 )
821                            {
822                                return -1;
823                            }
824    
825                            String decodedAttr = decode( attribute );
826    
827                            if ( !hAttributes.contains( decodedAttr ) )
828                            {
829                                attributes.add( decodedAttr );
830                                hAttributes.add( decodedAttr );
831                            }
832                        }
833    
834                        start = i + 1;
835                    }
836                    else
837                    {
838                        hadComma = false;
839                    }
840    
841                    end++;
842                }
843    
844                if ( hadComma )
845                {
846    
847                    // We are not allowed to have a comma at the end of the
848                    // attributes
849                    return -1;
850                }
851                else
852                {
853    
854                    if ( end == start )
855                    {
856    
857                        // We don't have any attributes. This is valid.
858                        return end;
859                    }
860    
861                    // Store the last attribute
862                    // get the attribute. It must not be blank
863                    String attribute = null;
864    
865                    attribute = new String( chars, start, end - start ).trim();
866    
867                    if ( attribute.length() == 0 )
868                    {
869                        return -1;
870                    }
871    
872                    String decodedAttr = decode( attribute );
873    
874                    if ( !hAttributes.contains( decodedAttr ) )
875                    {
876                        attributes.add( decodedAttr );
877                        hAttributes.add( decodedAttr );
878                    }
879                }
880    
881                return end;
882            }
883            catch ( URIException ue )
884            {
885                return -1;
886            }
887        }
888    
889    
890        /**
891         * Parse the filter part. We will use the FilterParserImpl class
892         * 
893         * @param chars The char array to be checked
894         * @param pos the starting position
895         * @return -1 if the char array does not contains a filter
896         */
897        private int parseFilter( char[] chars, int pos )
898        {
899    
900            int end = pos;
901    
902            for ( int i = pos; ( i < chars.length ) && ( chars[i] != '?' ); i++ )
903            {
904                end++;
905            }
906    
907            if ( end == pos )
908            {
909                // We have no filter
910                return end;
911            }
912    
913            try
914            {
915                filter = decode( new String( chars, pos, end - pos ) );
916                FilterParser.parse( filter );
917            }
918            catch ( URIException ue )
919            {
920                return -1;
921            }
922            catch ( ParseException pe )
923            {
924                return -1;
925            }
926    
927            return end;
928        }
929    
930    
931        /**
932         * Parse the scope part.
933         * 
934         * @param chars The char array to be checked
935         * @param pos the starting position
936         * @return -1 if the char array does not contains a scope
937         */
938        private int parseScope( char[] chars, int pos )
939        {
940    
941            if ( StringTools.isCharASCII( chars, pos, 'b' ) || StringTools.isCharASCII( chars, pos, 'B' ) )
942            {
943                pos++;
944    
945                if ( StringTools.isCharASCII( chars, pos, 'a' ) || StringTools.isCharASCII( chars, pos, 'A' ) )
946                {
947                    pos++;
948    
949                    if ( StringTools.isCharASCII( chars, pos, 's' ) || StringTools.isCharASCII( chars, pos, 'S' ) )
950                    {
951                        pos++;
952    
953                        if ( StringTools.isCharASCII( chars, pos, 'e' ) || StringTools.isCharASCII( chars, pos, 'E' ) )
954                        {
955                            pos++;
956                            scope = SearchScope.OBJECT;
957                            return pos;
958                        }
959                    }
960                }
961            }
962            else if ( StringTools.isCharASCII( chars, pos, 'o' ) || StringTools.isCharASCII( chars, pos, 'O' ) )
963            {
964                pos++;
965    
966                if ( StringTools.isCharASCII( chars, pos, 'n' ) || StringTools.isCharASCII( chars, pos, 'N' ) )
967                {
968                    pos++;
969    
970                    if ( StringTools.isCharASCII( chars, pos, 'e' ) || StringTools.isCharASCII( chars, pos, 'E' ) )
971                    {
972                        pos++;
973    
974                        scope = SearchScope.ONELEVEL;
975                        return pos;
976                    }
977                }
978            }
979            else if ( StringTools.isCharASCII( chars, pos, 's' ) || StringTools.isCharASCII( chars, pos, 'S' ) )
980            {
981                pos++;
982    
983                if ( StringTools.isCharASCII( chars, pos, 'u' ) || StringTools.isCharASCII( chars, pos, 'U' ) )
984                {
985                    pos++;
986    
987                    if ( StringTools.isCharASCII( chars, pos, 'b' ) || StringTools.isCharASCII( chars, pos, 'B' ) )
988                    {
989                        pos++;
990    
991                        scope = SearchScope.SUBTREE;
992                        return pos;
993                    }
994                }
995            }
996            else if ( StringTools.isCharASCII( chars, pos, '?' ) )
997            {
998                // An empty scope. This is valid
999                return pos;
1000            }
1001            else if ( pos == chars.length )
1002            {
1003                // An empty scope at the end of the URL. This is valid
1004                return pos;
1005            }
1006    
1007            // The scope is not one of "one", "sub" or "base". It's an error
1008            return -1;
1009        }
1010    
1011    
1012        /**
1013         * Parse extensions and critical extensions. 
1014         * 
1015         * The grammar is : 
1016         * extensions ::= extension [ ',' extension ]* 
1017         * extension ::= [ '!' ] ( token | ( 'x-' | 'X-' ) token ) ) [ '=' exvalue ]
1018         * 
1019         * @param chars The char array to be checked
1020         * @param pos the starting position
1021         * @return -1 if the char array does not contains valid extensions or
1022         *         critical extensions
1023         */
1024        private int parseExtensions( char[] chars, int pos )
1025        {
1026            int start = pos;
1027            boolean isCritical = false;
1028            boolean isNewExtension = true;
1029            boolean hasValue = false;
1030            String extension = null;
1031            String value = null;
1032    
1033            if ( pos == chars.length )
1034            {
1035                return pos;
1036            }
1037    
1038            try
1039            {
1040                for ( int i = pos; ( i < chars.length ); i++ )
1041                {
1042                    if ( StringTools.isCharASCII( chars, i, ',' ) )
1043                    {
1044                        if ( isNewExtension )
1045                        {
1046                            // a ',' is not allowed when we have already had one
1047                            // or if we just started to parse the extensions.
1048                            return -1;
1049                        }
1050                        else
1051                        {
1052                            if ( extension == null )
1053                            {
1054                                extension = decode( new String( chars, start, i - start ) ).trim();
1055                            }
1056                            else
1057                            {
1058                                value = decode( new String( chars, start, i - start ) ).trim();
1059                            }
1060    
1061                            Extension ext = new Extension( isCritical, extension, value );
1062                            extensionList.add( ext );
1063    
1064                            isNewExtension = true;
1065                            hasValue = false;
1066                            isCritical = false;
1067                            start = i + 1;
1068                            extension = null;
1069                            value = null;
1070                        }
1071                    }
1072                    else if ( StringTools.isCharASCII( chars, i, '=' ) )
1073                    {
1074                        if ( hasValue )
1075                        {
1076                            // We may have two '=' for the same extension
1077                            continue;
1078                        }
1079    
1080                        // An optionnal value
1081                        extension = decode( new String( chars, start, i - start ) ).trim();
1082    
1083                        if ( extension.length() == 0 )
1084                        {
1085                            // We must have an extension
1086                            return -1;
1087                        }
1088    
1089                        hasValue = true;
1090                        start = i + 1;
1091                    }
1092                    else if ( StringTools.isCharASCII( chars, i, '!' ) )
1093                    {
1094                        if ( hasValue )
1095                        {
1096                            // We may have two '!' in the value
1097                            continue;
1098                        }
1099    
1100                        if ( !isNewExtension )
1101                        {
1102                            // '!' must appears first
1103                            return -1;
1104                        }
1105    
1106                        isCritical = true;
1107                        start++;
1108                    }
1109                    else
1110                    {
1111                        isNewExtension = false;
1112                    }
1113                }
1114    
1115                if ( extension == null )
1116                {
1117                    extension = decode( new String( chars, start, chars.length - start ) ).trim();
1118                }
1119                else
1120                {
1121                    value = decode( new String( chars, start, chars.length - start ) ).trim();
1122                }
1123    
1124                Extension ext = new Extension( isCritical, extension, value );
1125                extensionList.add( ext );
1126    
1127                return chars.length;
1128            }
1129            catch ( URIException ue )
1130            {
1131                return -1;
1132            }
1133        }
1134    
1135    
1136        /**
1137         * Encode a String to avoid special characters.
1138         *
1139         * 
1140         * RFC 4516, section 2.1. (Percent-Encoding)
1141         *
1142         * A generated LDAP URL MUST consist only of the restricted set of
1143         * characters included in one of the following three productions defined
1144         * in [RFC3986]:
1145         *
1146         *   <reserved>
1147         *   <unreserved>
1148         *   <pct-encoded>
1149         *
1150         * Implementations SHOULD accept other valid UTF-8 strings [RFC3629] as
1151         * input.  An octet MUST be encoded using the percent-encoding mechanism
1152         * described in section 2.1 of [RFC3986] in any of these situations:
1153         * 
1154         *  The octet is not in the reserved set defined in section 2.2 of
1155         *  [RFC3986] or in the unreserved set defined in section 2.3 of
1156         *  [RFC3986].
1157         *
1158         *  It is the single Reserved character '?' and occurs inside a <dn>,
1159         *  <filter>, or other element of an LDAP URL.
1160         *
1161         *  It is a comma character ',' that occurs inside an <exvalue>.
1162         *
1163         *
1164         * RFC 3986, section 2.2 (Reserved Characters)
1165         * 
1166         * reserved    = gen-delims / sub-delims
1167         * gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@"
1168         * sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
1169         *              / "*" / "+" / "," / ";" / "="
1170         *             
1171         *             
1172         * RFC 3986, section 2.3 (Unreserved Characters)
1173         * 
1174         * unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
1175         *
1176         * 
1177         * @param url The String to encode
1178         * @param doubleEncode Set if we need to encode the comma
1179         * @return An encoded string
1180         */
1181        public static String urlEncode( String url, boolean doubleEncode )
1182        {
1183            StringBuffer sb = new StringBuffer();
1184    
1185            for ( int i = 0; i < url.length(); i++ )
1186            {
1187                char c = url.charAt( i );
1188    
1189                switch ( c )
1190    
1191                {
1192                    // reserved and unreserved characters:
1193                    // just append to the buffer
1194    
1195                    // reserved gen-delims, excluding '?'
1196                    // gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@"
1197                    case ':':
1198                    case '/':
1199                    case '#':
1200                    case '[':
1201                    case ']':
1202                    case '@':
1203    
1204                        // reserved sub-delims, excluding ','
1205                        // sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
1206                        //               / "*" / "+" / "," / ";" / "="
1207                    case '!':
1208                    case '$':
1209                    case '&':
1210                    case '\'':
1211                    case '(':
1212                    case ')':
1213                    case '*':
1214                    case '+':
1215                    case ';':
1216                    case '=':
1217    
1218                        // unreserved
1219                        // unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
1220                    case 'a':
1221                    case 'b':
1222                    case 'c':
1223                    case 'd':
1224                    case 'e':
1225                    case 'f':
1226                    case 'g':
1227                    case 'h':
1228                    case 'i':
1229                    case 'j':
1230                    case 'k':
1231                    case 'l':
1232                    case 'm':
1233                    case 'n':
1234                    case 'o':
1235                    case 'p':
1236                    case 'q':
1237                    case 'r':
1238                    case 's':
1239                    case 't':
1240                    case 'u':
1241                    case 'v':
1242                    case 'w':
1243                    case 'x':
1244                    case 'y':
1245                    case 'z':
1246    
1247                    case 'A':
1248                    case 'B':
1249                    case 'C':
1250                    case 'D':
1251                    case 'E':
1252                    case 'F':
1253                    case 'G':
1254                    case 'H':
1255                    case 'I':
1256                    case 'J':
1257                    case 'K':
1258                    case 'L':
1259                    case 'M':
1260                    case 'N':
1261                    case 'O':
1262                    case 'P':
1263                    case 'Q':
1264                    case 'R':
1265                    case 'S':
1266                    case 'T':
1267                    case 'U':
1268                    case 'V':
1269                    case 'W':
1270                    case 'X':
1271                    case 'Y':
1272                    case 'Z':
1273    
1274                    case '0':
1275                    case '1':
1276                    case '2':
1277                    case '3':
1278                    case '4':
1279                    case '5':
1280                    case '6':
1281                    case '7':
1282                    case '8':
1283                    case '9':
1284    
1285                    case '-':
1286                    case '.':
1287                    case '_':
1288                    case '~':
1289    
1290                        sb.append( c );
1291                        break;
1292    
1293                    case ',':
1294    
1295                        // special case for comma
1296                        if ( doubleEncode )
1297                        {
1298                            sb.append( "%2c" );
1299                        }
1300                        else
1301                        {
1302                            sb.append( c );
1303                        }
1304                        break;
1305    
1306                    default:
1307    
1308                        // percent encoding
1309                        byte[] bytes = StringTools.charToBytes( c );
1310                        char[] hex = Hex.encodeHex( bytes );
1311                        for ( int j = 0; j < hex.length; j++ )
1312                        {
1313                            if ( j % 2 == 0 )
1314                            {
1315                                sb.append( '%' );
1316                            }
1317                            sb.append( hex[j] );
1318    
1319                        }
1320    
1321                        break;
1322                }
1323            }
1324    
1325            return sb.toString();
1326        }
1327    
1328    
1329        /**
1330         * Get a string representation of a LdapURL.
1331         * 
1332         * @return A LdapURL string
1333         * @see LdapURL#forceScopeRendering
1334         */
1335        public String toString()
1336        {
1337            StringBuffer sb = new StringBuffer();
1338    
1339            sb.append( scheme );
1340    
1341            sb.append( ( host == null ) ? "" : host );
1342    
1343            if ( port != -1 )
1344            {
1345                sb.append( ':' ).append( port );
1346            }
1347    
1348            if ( dn != null )
1349            {
1350                sb.append( '/' ).append( urlEncode( dn.getName(), false ) );
1351    
1352                if ( attributes.size() != 0 || forceScopeRendering
1353                    || ( ( scope != SearchScope.OBJECT ) || ( filter != null ) || ( extensionList.size() != 0 ) ) )
1354                {
1355                    sb.append( '?' );
1356    
1357                    boolean isFirst = true;
1358    
1359                    for ( String attribute : attributes )
1360                    {
1361                        if ( isFirst )
1362                        {
1363                            isFirst = false;
1364                        }
1365                        else
1366                        {
1367                            sb.append( ',' );
1368                        }
1369    
1370                        sb.append( urlEncode( attribute, false ) );
1371                    }
1372                }
1373    
1374                if ( forceScopeRendering )
1375                {
1376                    sb.append( '?' );
1377    
1378                    sb.append( scope.getLdapUrlValue() );
1379                }
1380    
1381                else
1382                {
1383                    if ( ( scope != SearchScope.OBJECT ) || ( filter != null ) || ( extensionList.size() != 0 ) )
1384                    {
1385                        sb.append( '?' );
1386    
1387                        switch ( scope )
1388                        {
1389                            case ONELEVEL:
1390                            case SUBTREE:
1391                                sb.append( scope.getLdapUrlValue() );
1392                                break;
1393    
1394                            default:
1395                                break;
1396                        }
1397    
1398                        if ( ( filter != null ) || ( ( extensionList.size() != 0 ) ) )
1399                        {
1400                            sb.append( "?" );
1401    
1402                            if ( filter != null )
1403                            {
1404                                sb.append( urlEncode( filter, false ) );
1405                            }
1406    
1407                            if ( ( extensionList.size() != 0 ) )
1408                            {
1409                                sb.append( '?' );
1410    
1411                                boolean isFirst = true;
1412    
1413                                if ( extensionList.size() != 0 )
1414                                {
1415                                    for ( Extension extension : extensionList )
1416                                    {
1417                                        if ( !isFirst )
1418                                        {
1419                                            sb.append( ',' );
1420                                        }
1421                                        else
1422                                        {
1423                                            isFirst = false;
1424                                        }
1425    
1426                                        if ( extension.isCritical )
1427                                        {
1428                                            sb.append( '!' );
1429                                        }
1430                                        sb.append( urlEncode( extension.type, false ) );
1431    
1432                                        if ( extension.value != null )
1433                                        {
1434                                            sb.append( '=' );
1435                                            sb.append( urlEncode( extension.value, true ) );
1436                                        }
1437                                    }
1438                                }
1439                            }
1440                        }
1441                    }
1442                }
1443            }
1444            else
1445            {
1446                sb.append( '/' );
1447            }
1448    
1449            return sb.toString();
1450        }
1451    
1452    
1453        /**
1454         * @return Returns the attributes.
1455         */
1456        public List<String> getAttributes()
1457        {
1458            return attributes;
1459        }
1460    
1461    
1462        /**
1463         * @return Returns the dn.
1464         */
1465        public DN getDn()
1466        {
1467            return dn;
1468        }
1469    
1470    
1471        /**
1472         * @return Returns the extensions.
1473         */
1474        public List<Extension> getExtensions()
1475        {
1476            return extensionList;
1477        }
1478    
1479    
1480        /**
1481         * Gets the extension.
1482         * 
1483         * @param type the extension type, case-insensitive
1484         * 
1485         * @return Returns the extension, null if this URL does not contain 
1486         *         such an extension.
1487         */
1488        public Extension getExtension( String type )
1489        {
1490            for ( Extension extension : getExtensions() )
1491            {
1492                if ( extension.getType().equalsIgnoreCase( type ) )
1493                {
1494                    return extension;
1495                }
1496            }
1497            return null;
1498        }
1499    
1500    
1501        /**
1502         * Gets the extension value.
1503         * 
1504         * @param type the extension type, case-insensitive
1505         * 
1506         * @return Returns the extension value, null if this URL does not  
1507         *         contain such an extension or if the extension value is null.
1508         */
1509        public String getExtensionValue( String type )
1510        {
1511            for ( Extension extension : getExtensions() )
1512            {
1513                if ( extension.getType().equalsIgnoreCase( type ) )
1514                {
1515                    return extension.getValue();
1516                }
1517            }
1518            return null;
1519        }
1520    
1521    
1522        /**
1523         * @return Returns the filter.
1524         */
1525        public String getFilter()
1526        {
1527            return filter;
1528        }
1529    
1530    
1531        /**
1532         * @return Returns the host.
1533         */
1534        public String getHost()
1535        {
1536            return host;
1537        }
1538    
1539    
1540        /**
1541         * @return Returns the port.
1542         */
1543        public int getPort()
1544        {
1545            return port;
1546        }
1547    
1548    
1549        /**
1550         * Returns the scope, one of {@link SearchScope.OBJECT}, 
1551         * {@link SearchScope.ONELEVEL} or {@link SearchScope.SUBTREE}.
1552         * 
1553         * @return Returns the scope.
1554         */
1555        public SearchScope getScope()
1556        {
1557            return scope;
1558        }
1559    
1560    
1561        /**
1562         * @return Returns the scheme.
1563         */
1564        public String getScheme()
1565        {
1566            return scheme;
1567        }
1568    
1569    
1570        /**
1571         * @return the number of bytes for this LdapURL
1572         */
1573        public int getNbBytes()
1574        {
1575            return ( bytes != null ? bytes.length : 0 );
1576        }
1577    
1578    
1579        /**
1580         * @return a reference on the interned bytes representing this LdapURL
1581         */
1582        public byte[] getBytesReference()
1583        {
1584            return bytes;
1585        }
1586    
1587    
1588        /**
1589         * @return a copy of the bytes representing this LdapURL
1590         */
1591        public byte[] getBytesCopy()
1592        {
1593            if ( bytes != null )
1594            {
1595                byte[] copy = new byte[bytes.length];
1596                System.arraycopy( bytes, 0, copy, 0, bytes.length );
1597                return copy;
1598            }
1599            else
1600            {
1601                return null;
1602            }
1603        }
1604    
1605    
1606        /**
1607         * @return the LdapURL as a String
1608         */
1609        public String getString()
1610        {
1611            return string;
1612        }
1613    
1614    
1615        /**
1616         * Compute the instance's hash code
1617         * @return the instance's hash code 
1618         */
1619        public int hashCode()
1620        {
1621            return this.toString().hashCode();
1622        }
1623    
1624    
1625        public boolean equals( Object obj )
1626        {
1627            if ( this == obj )
1628            {
1629                return true;
1630            }
1631            if ( obj == null )
1632            {
1633                return false;
1634            }
1635            if ( getClass() != obj.getClass() )
1636            {
1637                return false;
1638            }
1639    
1640            final LdapURL other = ( LdapURL ) obj;
1641            return this.toString().equals( other.toString() );
1642        }
1643    
1644    
1645        /**
1646         * Sets the scheme. Must be "ldap://" or "ldaps://", otherwise "ldap://" is assumed as default.
1647         * 
1648         * @param scheme the new scheme
1649         */
1650        public void setScheme( String scheme )
1651        {
1652            if ( scheme != null && LDAP_SCHEME.equals( scheme ) || LDAPS_SCHEME.equals( scheme ) )
1653            {
1654                this.scheme = scheme;
1655            }
1656            else
1657            {
1658                this.scheme = LDAP_SCHEME;
1659            }
1660    
1661        }
1662    
1663    
1664        /**
1665         * Sets the host.
1666         * 
1667         * @param host the new host
1668         */
1669        public void setHost( String host )
1670        {
1671            this.host = host;
1672        }
1673    
1674    
1675        /**
1676         * Sets the port. Must be between 1 and 65535, otherwise -1 is assumed as default.
1677         * 
1678         * @param port the new port
1679         */
1680        public void setPort( int port )
1681        {
1682            if ( port < 1 || port > 65535 )
1683            {
1684                this.port = -1;
1685            }
1686            else
1687            {
1688                this.port = port;
1689            }
1690        }
1691    
1692    
1693        /**
1694         * Sets the dn.
1695         * 
1696         * @param dn the new dn
1697         */
1698        public void setDn( DN dn )
1699        {
1700            this.dn = dn;
1701        }
1702    
1703    
1704        /**
1705         * Sets the attributes, null removes all existing attributes.
1706         * 
1707         * @param attributes the new attributes
1708         */
1709        public void setAttributes( List<String> attributes )
1710        {
1711            if ( attributes == null )
1712            {
1713                this.attributes.clear();
1714            }
1715            else
1716            {
1717                this.attributes = attributes;
1718            }
1719        }
1720    
1721    
1722        /**
1723         * Sets the scope. Must be one of {@link SearchScope.OBJECT}, 
1724         * {@link SearchScope.ONELEVEL} or {@link SearchScope.SUBTREE},
1725         * otherwise {@link SearchScope.OBJECT} is assumed as default.
1726         * 
1727         * @param scope the new scope
1728         */
1729        public void setScope( int scope )
1730        {
1731            try 
1732            {
1733                this.scope = SearchScope.getSearchScope( scope );
1734            }
1735            catch ( IllegalArgumentException iae )
1736            {
1737                this.scope = SearchScope.OBJECT;
1738            }
1739        }
1740    
1741    
1742        /**
1743         * Sets the scope. Must be one of {@link SearchScope.OBJECT}, 
1744         * {@link SearchScope.ONELEVEL} or {@link SearchScope.SUBTREE},
1745         * otherwise {@link SearchScope.OBJECT} is assumed as default.
1746         * 
1747         * @param scope the new scope
1748         */
1749        public void setScope( SearchScope scope )
1750        {
1751            if ( scope == null )
1752            {
1753                this.scope = SearchScope.OBJECT;
1754            }
1755            else
1756            {
1757                this.scope = scope;
1758            }
1759        }
1760    
1761    
1762        /**
1763         * Sets the filter.
1764         * 
1765         * @param filter the new filter
1766         */
1767        public void setFilter( String filter )
1768        {
1769            this.filter = filter;
1770        }
1771    
1772    
1773        /**
1774         * If set to true forces the toString method to render the scope 
1775         * regardless of optional nature.  Use this when you want explicit
1776         * search URL scope rendering.
1777         * 
1778         * @param forceScopeRendering the forceScopeRendering to set
1779         */
1780        public void setForceScopeRendering( boolean forceScopeRendering )
1781        {
1782            this.forceScopeRendering = forceScopeRendering;
1783        }
1784    
1785    
1786        /**
1787         * If set to true forces the toString method to render the scope 
1788         * regardless of optional nature.  Use this when you want explicit
1789         * search URL scope rendering.
1790         * 
1791         * @return the forceScopeRendering
1792         */
1793        public boolean isForceScopeRendering()
1794        {
1795            return forceScopeRendering;
1796        }
1797    
1798        /**
1799         * An inner bean to hold extension information.
1800         *
1801         * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
1802         * @version $Rev: 919765 $, $Date: 2010-03-06 14:44:54 +0100 (Sam, 06 mar 2010) $
1803         */
1804        public static class Extension
1805        {
1806            private boolean isCritical;
1807            private String type;
1808            private String value;
1809    
1810    
1811            /**
1812             * Creates a new instance of Extension.
1813             *
1814             * @param isCritical true for critical extension
1815             * @param type the extension type
1816             * @param value the extension value
1817             */
1818            public Extension( boolean isCritical, String type, String value )
1819            {
1820                super();
1821                this.isCritical = isCritical;
1822                this.type = type;
1823                this.value = value;
1824            }
1825    
1826    
1827            /**
1828             * Checks if is critical.
1829             * 
1830             * @return true, if is critical
1831             */
1832            public boolean isCritical()
1833            {
1834                return isCritical;
1835            }
1836    
1837    
1838            /**
1839             * Sets the critical.
1840             * 
1841             * @param isCritical the new critical
1842             */
1843            public void setCritical( boolean isCritical )
1844            {
1845                this.isCritical = isCritical;
1846            }
1847    
1848    
1849            /**
1850             * Gets the type.
1851             * 
1852             * @return the type
1853             */
1854            public String getType()
1855            {
1856                return type;
1857            }
1858    
1859    
1860            /**
1861             * Sets the type.
1862             * 
1863             * @param type the new type
1864             */
1865            public void setType( String type )
1866            {
1867                this.type = type;
1868            }
1869    
1870    
1871            /**
1872             * Gets the value.
1873             * 
1874             * @return the value
1875             */
1876            public String getValue()
1877            {
1878                return value;
1879            }
1880    
1881    
1882            /**
1883             * Sets the value.
1884             * 
1885             * @param value the new value
1886             */
1887            public void setValue( String value )
1888            {
1889                this.value = value;
1890            }
1891        }
1892    
1893    }