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.text.ParseException;
024    import java.util.Arrays;
025    import java.util.Iterator;
026    import java.util.List;
027    
028    import javax.naming.NamingEnumeration;
029    import javax.naming.NamingException;
030    import javax.naming.directory.Attribute;
031    import javax.naming.directory.Attributes;
032    import javax.naming.directory.BasicAttribute;
033    import javax.naming.directory.BasicAttributes;
034    import javax.naming.directory.InvalidAttributeIdentifierException;
035    
036    import org.apache.directory.shared.i18n.I18n;
037    import org.apache.directory.shared.ldap.entry.Entry;
038    import org.apache.directory.shared.ldap.entry.EntryAttribute;
039    import org.apache.directory.shared.ldap.entry.Modification;
040    import org.apache.directory.shared.ldap.entry.Value;
041    import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute;
042    import org.apache.directory.shared.ldap.entry.client.DefaultClientEntry;
043    import org.apache.directory.shared.ldap.exception.LdapException;
044    import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeTypeException;
045    import org.apache.directory.shared.ldap.name.DN;
046    import org.apache.directory.shared.ldap.schema.AttributeType;
047    import org.apache.directory.shared.ldap.schema.MatchingRule;
048    import org.apache.directory.shared.ldap.schema.Normalizer;
049    import org.apache.directory.shared.ldap.schema.normalizers.NoOpNormalizer;
050    
051    
052    /**
053     * A set of utility fuctions for working with Attributes.
054     * 
055     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
056     * @version $Rev: 923893 $
057     */
058    public class AttributeUtils
059    {
060        /**
061         * Correctly removes an attribute from an entry using it's attributeType information.
062         * 
063         * @param type the attributeType of the attribute to remove
064         * @param entry the entry to remove the attribute from 
065         * @return the Attribute that is removed
066         */
067        public static Attribute removeAttribute( AttributeType type, Attributes entry )
068        {
069            Attribute attr = entry.get( type.getOid() );
070    
071            if ( attr == null )
072            {
073                List<String> aliases = type.getNames();
074    
075                for ( String alias : aliases )
076                {
077                    attr = entry.get( alias );
078    
079                    if ( attr != null )
080                    {
081                        return entry.remove( attr.getID() );
082                    }
083                }
084            }
085    
086            if ( attr == null )
087            {
088                return null;
089            }
090    
091            return entry.remove( attr.getID() );
092        }
093    
094    
095        /**
096         * Compare two values and return true if they are equal.
097         * 
098         * @param value1 The first value
099         * @param value2 The second value
100         * @return true if both value are null or if they are equal.
101         */
102        public static final boolean equals( Object value1, Object value2 )
103        {
104            if ( value1 == value2 )
105            {
106                return true;
107            }
108    
109            if ( value1 == null )
110            {
111                return ( value2 == null );
112            }
113    
114            if ( value1 instanceof byte[] )
115            {
116                if ( value2 instanceof byte[] )
117                {
118                    return Arrays.equals( ( byte[] ) value1, ( byte[] ) value2 );
119                }
120                else
121                {
122                    return false;
123                }
124            }
125            else
126            {
127                return value1.equals( value2 );
128            }
129        }
130    
131    
132        /**
133         * Clone the value. An attribute value is supposed to be either a String
134         * or a byte array. If it's a String, then we just return it ( as String
135         * is immutable, we don't need to copy it). If it's a bu=yte array, we
136         * create a new byte array and copy the bytes into it.
137         * 
138         * @param value The value to clone
139         * @return The cloned value
140         */
141        public static Object cloneValue( Object value )
142        {
143            // First copy the value
144            Object newValue = null;
145    
146            if ( value instanceof byte[] )
147            {
148                newValue = ( ( byte[] ) value ).clone();
149            }
150            else
151            {
152                newValue = value;
153            }
154    
155            return newValue;
156        }
157    
158    
159        /**
160         * Switch from a BasicAttribute to a AttributeImpl. This is
161         * necessary to allow cloning to be correctly handled.
162         * 
163         * @param attribute The attribute to transform
164         * @return A instance of AttributeImpl
165         */
166        public static final Attribute toBasicAttribute( Attribute attribute )
167        {
168            if ( attribute instanceof BasicAttribute )
169            {
170                // Just return the attribute
171                return attribute;
172            }
173            else
174            {
175                // Create a new AttributeImpl from the original attribute
176                Attribute newAttribute = new BasicAttribute( attribute.getID() );
177    
178                try
179                {
180                    NamingEnumeration<?> values = attribute.getAll();
181    
182                    while ( values.hasMoreElements() )
183                    {
184                        newAttribute.add( cloneValue( values.next() ) );
185                    }
186    
187                    return newAttribute;
188                }
189                catch ( NamingException ne )
190                {
191                    return newAttribute;
192                }
193            }
194        }
195    
196    
197        /**
198         * Utility method to extract an attribute from Attributes object using
199         * all combinationos of the name including aliases.
200         * 
201         * @param attrs the Attributes to get the Attribute object from
202         * @param type the attribute type specification
203         * @return an Attribute with matching the attributeType spec or null
204         */
205        public static final Attribute getAttribute( Attributes attrs, AttributeType type )
206        {
207            // check if the attribute's OID is used
208            Attribute attr = attrs.get( type.getOid() );
209    
210            if ( attr != null )
211            {
212                return attr;
213            }
214    
215            // optimization bypass to avoid cost of the loop below
216            if ( type.getNames().size() == 1 )
217            {
218                attr = attrs.get( type.getNames().get( 0 ) );
219    
220                if ( attr != null )
221                {
222                    return attr;
223                }
224            }
225    
226            // iterate through aliases
227            for ( String alias : type.getNames() )
228            {
229                attr = attrs.get( alias );
230    
231                if ( attr != null )
232                {
233                    return attr;
234                }
235            }
236    
237            return null;
238        }
239    
240    
241        /**
242         * Check if an attribute contains a specific value, using the associated matchingRule for that
243         *
244         * @param attr The attribute we are searching in
245         * @param compared The object we are looking for
246         * @param type The attribute type
247         * @return <code>true</code> if the value exists in the attribute</code>
248         * @throws LdapException If something went wrong while accessing the data
249         */
250        public static boolean containsValue( Attribute attr, Value<?> compared, AttributeType type ) throws LdapException
251        {
252            // quick bypass test
253            if ( attr.contains( compared ) )
254            {
255                return true;
256            }
257    
258            MatchingRule matchingRule = type.getEquality();
259    
260            Normalizer normalizer = null;
261    
262            if ( matchingRule != null )
263            {
264                normalizer = matchingRule.getNormalizer();
265            }
266            else
267            {
268                normalizer = new NoOpNormalizer( type.getOid() );
269            }
270    
271            if ( type.getSyntax().isHumanReadable() )
272            {
273                try
274                {
275                    String comparedStr = normalizer.normalize( compared.getString() );
276                    
277                    for ( NamingEnumeration<?> values = attr.getAll(); values.hasMoreElements(); /**/)
278                    {
279                        String value = ( String ) values.nextElement();
280                        if ( comparedStr.equals( normalizer.normalize( value ) ) )
281                        {
282                            return true;
283                        }
284                    }
285                }
286                catch( NamingException e )
287                {
288                    throw new LdapException( e.getMessage() );
289                }
290            }
291            else
292            {
293                byte[] comparedBytes = null;
294    
295                if ( !compared.isBinary() )
296                {
297                    if ( compared.getString().length() < 3 )
298                    {
299                        return false;
300                    }
301    
302                    // Transform the String to a byte array
303                    int state = 1;
304                    comparedBytes = new byte[compared.getString().length() / 3];
305                    int pos = 0;
306    
307                    for ( char c : compared.getString().toCharArray() )
308                    {
309                        switch ( state )
310                        {
311                            case 1:
312                                if ( c != '\\' )
313                                {
314                                    return false;
315                                }
316    
317                                state++;
318                                break;
319    
320                            case 2:
321                                int high = StringTools.getHexValue( c );
322    
323                                if ( high == -1 )
324                                {
325                                    return false;
326                                }
327    
328                                comparedBytes[pos] = ( byte ) ( high << 4 );
329    
330                                state++;
331                                break;
332    
333                            case 3:
334                                int low = StringTools.getHexValue( c );
335    
336                                if ( low == -1 )
337                                {
338                                    return false;
339                                }
340    
341                                comparedBytes[pos] += ( byte ) low;
342                                pos++;
343    
344                                state = 1;
345                                break;
346                        }
347                    }
348                }
349                else
350                {
351                    comparedBytes = compared.getBytes();
352                }
353    
354                try
355                {
356                    for ( NamingEnumeration<?> values = attr.getAll(); values.hasMoreElements(); /**/)
357                    {
358                        Object value = values.nextElement();
359        
360                        if ( value instanceof byte[] )
361                        {
362                            if ( ArrayUtils.isEquals( comparedBytes, value ) )
363                            {
364                                return true;
365                            }
366                        }
367                    }
368                }
369                catch ( NamingException ne )
370                {
371                    throw new LdapException( ne.getMessage() );
372                }
373            }
374    
375            return false;
376        }
377    
378    
379        /**
380         * Check if an attribute contains a value. The test is case insensitive,
381         * and the value is supposed to be a String. If the value is a byte[],
382         * then the case sensitivity is useless.
383         *
384         * @param attr The attribute to check
385         * @param value The value to look for
386         * @return true if the value is present in the attribute
387         */
388        public static boolean containsValueCaseIgnore( Attribute attr, Object value )
389        {
390            // quick bypass test
391            if ( attr.contains( value ) )
392            {
393                return true;
394            }
395    
396            try
397            {
398                if ( value instanceof String )
399                {
400                    String strVal = ( String ) value;
401    
402                    NamingEnumeration<?> attrVals = attr.getAll();
403    
404                    while ( attrVals.hasMoreElements() )
405                    {
406                        Object attrVal = attrVals.nextElement();
407    
408                        if ( attrVal instanceof String )
409                        {
410                            if ( strVal.equalsIgnoreCase( ( String ) attrVal ) )
411                            {
412                                return true;
413                            }
414                        }
415                    }
416                }
417                else
418                {
419                    byte[] valueBytes = ( byte[] ) value;
420    
421                    NamingEnumeration<?> attrVals = attr.getAll();
422    
423                    while ( attrVals.hasMoreElements() )
424                    {
425                        Object attrVal = attrVals.nextElement();
426    
427                        if ( attrVal instanceof byte[] )
428                        {
429                            if ( Arrays.equals( ( byte[] ) attrVal, valueBytes ) )
430                            {
431                                return true;
432                            }
433    
434                        }
435                    }
436                }
437            }
438            catch ( NamingException ne )
439            {
440                return false;
441            }
442    
443            return false;
444        }
445    
446    
447        /*
448        public static boolean containsAnyValues( Attribute attr, Object[] compared, AttributeType type )
449            throws NamingException
450        {
451            // quick bypass test
452            for ( Object object : compared )
453            {
454                if ( attr.contains( object ) )
455                {
456                    return true;
457                }
458            }
459    
460            Normalizer normalizer = type.getEquality().getNormalizer();
461    
462            if ( type.getSyntax().isHumanReadable() )
463            {
464                for ( Object object : compared )
465                {
466                    String comparedStr = ( String ) normalizer.normalize( object );
467    
468                    for ( int ii = attr.size(); ii >= 0; ii-- )
469                    {
470                        String value = ( String ) attr.get( ii );
471    
472                        if ( comparedStr.equals( normalizer.normalize( value ) ) )
473                        {
474                            return true;
475                        }
476                    }
477                }
478            }
479            else
480            {
481                for ( Object object : compared )
482                {
483                    byte[] comparedBytes = ( byte[] ) object;
484    
485                    for ( int ii = attr.size(); ii >= 0; ii-- )
486                    {
487                        if ( ArrayUtils.isEquals( comparedBytes, attr.get( ii ) ) )
488                        {
489                            return true;
490                        }
491                    }
492                }
493            }
494    
495            return false;
496        }
497        */
498    
499    
500        /**
501         * Creates a new attribute which contains the values representing the
502         * difference of two attributes. If both attributes are null then we cannot
503         * determine the attribute ID and an {@link IllegalArgumentException} is
504         * raised. Note that the order of arguments makes a difference.
505         * 
506         * @param attr0
507         *            the first attribute
508         * @param attr1
509         *            the second attribute
510         * @return a new attribute with the difference of values from both attribute
511         *         arguments
512         * @throws NamingException
513         *             if there are problems accessing attribute values
514         */
515        public static Attribute getDifference( Attribute attr0, Attribute attr1 ) throws NamingException
516        {
517            String id;
518    
519            if ( ( attr0 == null ) && ( attr1 == null ) )
520            {
521                throw new IllegalArgumentException( I18n.err( I18n.ERR_04339 ) );
522            }
523            else if ( attr0 == null )
524            {
525                return new BasicAttribute( attr1.getID() );
526            }
527            else if ( attr1 == null )
528            {
529                return ( Attribute ) attr0.clone();
530            }
531            else if ( !attr0.getID().equalsIgnoreCase( attr1.getID() ) )
532            {
533                throw new IllegalArgumentException( I18n.err( I18n.ERR_04340 ) );
534            }
535            else
536            {
537                id = attr0.getID();
538            }
539    
540            Attribute attr = new BasicAttribute( id );
541    
542            for ( int ii = 0; ii < attr0.size(); ii++ )
543            {
544                attr.add( attr0.get( ii ) );
545            }
546    
547            for ( int ii = 0; ii < attr1.size(); ii++ )
548            {
549                attr.remove( attr1.get( ii ) );
550            }
551    
552            return attr;
553        }
554    
555    
556        /**
557         * Creates a new attribute which contains the values representing the union
558         * of two attributes. If one attribute is null then the resultant attribute
559         * returned is a copy of the non-null attribute. If both are null then we
560         * cannot determine the attribute ID and an {@link IllegalArgumentException}
561         * is raised.
562         * 
563         * @param attr0
564         *            the first attribute
565         * @param attr1
566         *            the second attribute
567         * @return a new attribute with the union of values from both attribute
568         *         arguments
569         * @throws NamingException
570         *             if there are problems accessing attribute values
571         */
572        public static Attribute getUnion( Attribute attr0, Attribute attr1 ) throws NamingException
573        {
574            String id;
575    
576            if ( attr0 == null && attr1 == null )
577            {
578                throw new IllegalArgumentException( I18n.err( I18n.ERR_04341 ) );
579            }
580            else if ( attr0 == null )
581            {
582                id = attr1.getID();
583            }
584            else if ( attr1 == null )
585            {
586                id = attr0.getID();
587            }
588            else if ( !attr0.getID().equalsIgnoreCase( attr1.getID() ) )
589            {
590                throw new IllegalArgumentException( I18n.err( I18n.ERR_04342 ) );
591            }
592            else
593            {
594                id = attr0.getID();
595            }
596    
597            Attribute attr = new BasicAttribute( id );
598    
599            if ( attr0 != null )
600            {
601                for ( int ii = 0; ii < attr0.size(); ii++ )
602                {
603                    attr.add( attr0.get( ii ) );
604                }
605            }
606    
607            if ( attr1 != null )
608            {
609                for ( int ii = 0; ii < attr1.size(); ii++ )
610                {
611                    attr.add( attr1.get( ii ) );
612                }
613            }
614    
615            return attr;
616        }
617    
618    
619        /**
620         * Check if the attributes is a BasicAttributes, and if so, switch
621         * the case sensitivity to false to avoid tricky problems in the server.
622         * (Ldap attributeTypes are *always* case insensitive)
623         * 
624         * @param attributes The Attributes to check
625         */
626        public static Attributes toCaseInsensitive( Attributes attributes )
627        {
628            if ( attributes == null )
629            {
630                return attributes;
631            }
632    
633            if ( attributes instanceof BasicAttributes )
634            {
635                if ( attributes.isCaseIgnored() )
636                {
637                    // Just do nothing if the Attributes is already case insensitive
638                    return attributes;
639                }
640                else
641                {
642                    // Ok, bad news : we have to create a new BasicAttributes
643                    // which will be case insensitive
644                    Attributes newAttrs = new BasicAttributes( true );
645    
646                    NamingEnumeration<?> attrs = attributes.getAll();
647    
648                    if ( attrs != null )
649                    {
650                        // Iterate through the attributes now
651                        while ( attrs.hasMoreElements() )
652                        {
653                            newAttrs.put( ( Attribute ) attrs.nextElement() );
654                        }
655                    }
656    
657                    return newAttrs;
658                }
659            }
660            else
661            {
662                // we can safely return the attributes if it's not a BasicAttributes
663                return attributes;
664            }
665        }
666    
667    
668        /**
669         * Return a string representing the attributes with tabs in front of the
670         * string
671         * 
672         * @param tabs
673         *            Spaces to be added before the string
674         * @param attribute
675         *            The attribute to print
676         * @return A string
677         */
678        public static String toString( String tabs, Attribute attribute )
679        {
680            StringBuffer sb = new StringBuffer();
681    
682            sb.append( tabs ).append( "Attribute\n" );
683    
684            if ( attribute != null )
685            {
686                sb.append( tabs ).append( "    Type : '" ).append( attribute.getID() ).append( "'\n" );
687    
688                for ( int j = 0; j < attribute.size(); j++ )
689                {
690    
691                    try
692                    {
693                        Object attr = attribute.get( j );
694    
695                        if ( attr != null )
696                        {
697                            if ( attr instanceof String )
698                            {
699                                sb.append( tabs ).append( "        Val[" ).append( j ).append( "] : " ).append( attr )
700                                    .append( " \n" );
701                            }
702                            else if ( attr instanceof byte[] )
703                            {
704                                String string = StringTools.utf8ToString( ( byte[] ) attr );
705    
706                                sb.append( tabs ).append( "        Val[" ).append( j ).append( "] : " );
707                                sb.append( string ).append( '/' );
708                                sb.append( StringTools.dumpBytes( ( byte[] ) attr ) );
709                                sb.append( " \n" );
710                            }
711                            else
712                            {
713                                sb.append( tabs ).append( "        Val[" ).append( j ).append( "] : " ).append( attr )
714                                    .append( " \n" );
715                            }
716                        }
717                    }
718                    catch ( NamingException ne )
719                    {
720                        sb.append( "Bad attribute : " ).append( ne.getMessage() );
721                    }
722                }
723            }
724    
725            return sb.toString();
726        }
727    
728    
729        /**
730         * Return a string representing the attribute
731         * 
732         * @param attribute
733         *            The attribute to print
734         * @return A string
735         */
736        public static String toString( Attribute attribute )
737        {
738            return toString( "", attribute );
739        }
740    
741    
742        /**
743         * Return a string representing the attributes with tabs in front of the
744         * string
745         * 
746         * @param tabs
747         *            Spaces to be added before the string
748         * @param attributes
749         *            The attributes to print
750         * @return A string
751         */
752        public static String toString( String tabs, Attributes attributes )
753        {
754            StringBuffer sb = new StringBuffer();
755            sb.append( tabs ).append( "Attributes\n" );
756    
757            if ( attributes != null )
758            {
759                NamingEnumeration<?> attributesIterator = attributes.getAll();
760    
761                while ( attributesIterator.hasMoreElements() )
762                {
763                    Attribute attribute = ( Attribute ) attributesIterator.nextElement();
764                    sb.append( tabs ).append( attribute.toString() );
765                }
766            }
767    
768            return sb.toString();
769        }
770    
771    
772        /**
773         * Parse attribute's options :
774         * 
775         * options = *( ';' option )
776         * option = 1*keychar
777         * keychar = 'a'-z' | 'A'-'Z' / '0'-'9' / '-'
778         */
779        private static void parseOptions( String str, Position pos ) throws ParseException
780        {
781            while ( StringTools.isCharASCII( str, pos.start, ';' ) )
782            {
783                pos.start++;
784    
785                // We have an option
786                if ( !StringTools.isAlphaDigitMinus( str, pos.start ) )
787                {
788                    // We must have at least one keychar
789                    throw new ParseException( I18n.err( I18n.ERR_04343 ), pos.start );
790                }
791    
792                pos.start++;
793    
794                while ( StringTools.isAlphaDigitMinus( str, pos.start ) )
795                {
796                    pos.start++;
797                }
798            }
799        }
800    
801    
802        /**
803         * Parse a number :
804         * 
805         * number = '0' | '1'..'9' digits
806         * digits = '0'..'9'*
807         * 
808         * @return true if a number has been found
809         */
810        private static boolean parseNumber( String filter, Position pos )
811        {
812            char c = StringTools.charAt( filter, pos.start );
813    
814            switch ( c )
815            {
816                case '0':
817                    // If we get a starting '0', we should get out
818                    pos.start++;
819                    return true;
820    
821                case '1':
822                case '2':
823                case '3':
824                case '4':
825                case '5':
826                case '6':
827                case '7':
828                case '8':
829                case '9':
830                    pos.start++;
831                    break;
832    
833                default:
834                    // Not a number.
835                    return false;
836            }
837    
838            while ( StringTools.isDigit( filter, pos.start ) )
839            {
840                pos.start++;
841            }
842    
843            return true;
844        }
845    
846    
847        /**
848         * 
849         * Parse an OID.
850         *
851         * numericoid = number 1*( '.' number )
852         * number = '0'-'9' / ( '1'-'9' 1*'0'-'9' )
853         *
854         * @param str The OID to parse
855         * @param pos The current position in the string
856         * @return A valid OID
857         * @throws ParseException If we don't have a valid OID
858         */
859        public static void parseOID( String str, Position pos ) throws ParseException
860        {
861            // We have an OID
862            parseNumber( str, pos );
863    
864            // We must have at least one '.' number
865            if ( !StringTools.isCharASCII( str, pos.start, '.' ) )
866            {
867                throw new ParseException( I18n.err( I18n.ERR_04344 ), pos.start );
868            }
869    
870            pos.start++;
871    
872            if ( !parseNumber( str, pos ) )
873            {
874                throw new ParseException( I18n.err( I18n.ERR_04345 ), pos.start );
875            }
876    
877            while ( true )
878            {
879                // Break if we get something which is not a '.'
880                if ( !StringTools.isCharASCII( str, pos.start, '.' ) )
881                {
882                    break;
883                }
884    
885                pos.start++;
886    
887                if ( !parseNumber( str, pos ) )
888                {
889                    throw new ParseException(I18n.err( I18n.ERR_04345 ), pos.start );
890                }
891            }
892        }
893    
894    
895        /**
896         * Parse an attribute. The grammar is :
897         * attributedescription = attributetype options
898         * attributetype = oid
899         * oid = descr / numericoid
900         * descr = keystring
901         * numericoid = number 1*( '.' number )
902         * options = *( ';' option )
903         * option = 1*keychar
904         * keystring = leadkeychar *keychar
905         * leadkeychar = 'a'-z' | 'A'-'Z'
906         * keychar = 'a'-z' | 'A'-'Z' / '0'-'9' / '-'
907         * number = '0'-'9' / ( '1'-'9' 1*'0'-'9' )
908         *
909         * @param str The parsed attribute,
910         * @param pos The position of the attribute in the current string
911         * @return The parsed attribute if valid
912         */
913        public static String parseAttribute( String str, Position pos, boolean withOption ) throws ParseException
914        {
915            // We must have an OID or an DESCR first
916            char c = StringTools.charAt( str, pos.start );
917    
918            if ( c == '\0' )
919            {
920                throw new ParseException( I18n.err( I18n.ERR_04346 ), pos.start );
921            }
922    
923            int start = pos.start;
924    
925            if ( StringTools.isAlpha( c ) )
926            {
927                // A DESCR
928                pos.start++;
929    
930                while ( StringTools.isAlphaDigitMinus( str, pos.start ) )
931                {
932                    pos.start++;
933                }
934    
935                // Parse the options if needed
936                if ( withOption )
937                {
938                    parseOptions( str, pos );
939                }
940    
941                return str.substring( start, pos.start );
942            }
943            else if ( StringTools.isDigit( c ) )
944            {
945                // An OID
946                pos.start++;
947    
948                // Parse the OID
949                parseOID( str, pos );
950    
951                // Parse the options
952                if ( withOption )
953                {
954                    parseOptions( str, pos );
955                }
956    
957                return str.substring( start, pos.start );
958            }
959            else
960            {
961                throw new ParseException( I18n.err( I18n.ERR_04347 ), pos.start );
962            }
963        }
964    
965    
966        /**
967         * Return a string representing the attributes
968         * 
969         * @param attributes
970         *            The attributes to print
971         * @return A string
972         */
973        public static String toString( Attributes attributes )
974        {
975            return toString( "", attributes );
976        }
977    
978    
979        /**
980         * A method to apply a modification to an existing entry.
981         * 
982         * @param entry The entry on which we want to apply a modification
983         * @param modification the Modification to be applied
984         * @throws LdapException if some operation fails.
985         */
986        public static void applyModification( Entry entry, Modification modification ) throws LdapException
987        {
988            EntryAttribute modAttr = modification.getAttribute();
989            String modificationId = modAttr.getId();
990    
991            switch ( modification.getOperation() )
992            {
993                case ADD_ATTRIBUTE:
994                    EntryAttribute modifiedAttr = entry.get( modificationId );
995    
996                    if ( modifiedAttr == null )
997                    {
998                        // The attribute should be added.
999                        entry.put( modAttr );
1000                    }
1001                    else
1002                    {
1003                        // The attribute exists : the values can be different,
1004                        // so we will just add the new values to the existing ones.
1005                        for ( Value<?> value : modAttr )
1006                        {
1007                            // If the value already exist, nothing is done.
1008                            // Note that the attribute *must* have been
1009                            // normalized before.
1010                            modifiedAttr.add( value );
1011                        }
1012                    }
1013    
1014                    break;
1015    
1016                case REMOVE_ATTRIBUTE:
1017                    if ( modAttr.get() == null )
1018                    {
1019                        // We have no value in the ModificationItem attribute :
1020                        // we have to remove the whole attribute from the initial
1021                        // entry
1022                        entry.removeAttributes( modificationId );
1023                    }
1024                    else
1025                    {
1026                        // We just have to remove the values from the original
1027                        // entry, if they exist.
1028                        modifiedAttr = entry.get( modificationId );
1029    
1030                        if ( modifiedAttr == null )
1031                        {
1032                            break;
1033                        }
1034    
1035                        for ( Value<?> value : modAttr )
1036                        {
1037                            // If the value does not exist, nothing is done.
1038                            // Note that the attribute *must* have been
1039                            // normalized before.
1040                            modifiedAttr.remove( value );
1041                        }
1042    
1043                        if ( modifiedAttr.size() == 0 )
1044                        {
1045                            // If this was the last value, remove the attribute
1046                            entry.removeAttributes( modifiedAttr.getId() );
1047                        }
1048                    }
1049    
1050                    break;
1051    
1052                case REPLACE_ATTRIBUTE:
1053                    if ( modAttr.get() == null )
1054                    {
1055                        // If the modification does not have any value, we have
1056                        // to delete the attribute from the entry.
1057                        entry.removeAttributes( modificationId );
1058                    }
1059                    else
1060                    {
1061                        // otherwise, just substitute the existing attribute.
1062                        entry.put( modAttr );
1063                    }
1064    
1065                    break;
1066            }
1067        }
1068    
1069    
1070        /**
1071         * Check if an attribute contains a specific value and remove it using the associated
1072         * matchingRule for the attribute type supplied.
1073         *
1074         * @param attr the attribute we are searching in
1075         * @param compared the object we are looking for
1076         * @param type the attribute type
1077         * @return the value removed from the attribute, otherwise null
1078         * @throws NamingException if something went wrong while removing the value
1079         *
1080        public static Object removeValue( Attribute attr, Object compared, AttributeType type ) throws NamingException
1081        {
1082            // quick bypass test
1083            if ( attr.contains( compared ) )
1084            {
1085                return attr.remove( compared );
1086            }
1087    
1088            MatchingRule matchingRule = type.getEquality();
1089            Normalizer normalizer;
1090    
1091            if ( matchingRule != null )
1092            {
1093                normalizer = type.getEquality().getNormalizer();
1094            }
1095            else
1096            {
1097                normalizer = new NoOpNormalizer();
1098            }
1099    
1100            if ( type.getSyntax().isHumanReadable() )
1101            {
1102                String comparedStr = ( String ) normalizer.normalize( compared );
1103    
1104                for ( NamingEnumeration<?> values = attr.getAll(); values.hasMoreElements(); )
1105                {
1106                    String value = ( String ) values.nextElement();
1107                    if ( comparedStr.equals( normalizer.normalize( value ) ) )
1108                    {
1109                        return attr.remove( value );
1110                    }
1111                }
1112            }
1113            else
1114            {
1115                byte[] comparedBytes = null;
1116    
1117                if ( compared instanceof String )
1118                {
1119                    if ( ( ( String ) compared ).length() < 3 )
1120                    {
1121                        return null;
1122                    }
1123    
1124                    // Tansform the String to a byte array
1125                    int state = 1;
1126                    comparedBytes = new byte[( ( String ) compared ).length() / 3];
1127                    int pos = 0;
1128    
1129                    for ( char c : ( ( String ) compared ).toCharArray() )
1130                    {
1131                        switch ( state )
1132                        {
1133                            case 1:
1134                                if ( c != '\\' )
1135                                {
1136                                    return null;
1137                                }
1138    
1139                                state++;
1140                                break;
1141    
1142                            case 2:
1143                                int high = StringTools.getHexValue( c );
1144    
1145                                if ( high == -1 )
1146                                {
1147                                    return null;
1148                                }
1149    
1150                                comparedBytes[pos] = ( byte ) ( high << 4 );
1151    
1152                                state++;
1153                                break;
1154    
1155                            case 3:
1156                                int low = StringTools.getHexValue( c );
1157    
1158                                if ( low == -1 )
1159                                {
1160                                    return null;
1161                                }
1162    
1163                                comparedBytes[pos] += ( byte ) low;
1164                                pos++;
1165    
1166                                state = 1;
1167                                break;
1168                        }
1169                    }
1170                }
1171                else
1172                {
1173                    comparedBytes = ( byte[] ) compared;
1174                }
1175    
1176                for ( NamingEnumeration<?> values = attr.getAll(); values.hasMoreElements(); )
1177                {
1178                    Object value = values.nextElement();
1179    
1180                    if ( value instanceof byte[] )
1181                    {
1182                        if ( ArrayUtils.isEquals( comparedBytes, value ) )
1183                        {
1184                            return attr.remove( value );
1185                        }
1186                    }
1187                }
1188            }
1189    
1190            return null;
1191        }
1192    
1193    
1194        /**
1195         * Convert a BasicAttributes or a AttributesImpl to a ServerEntry
1196         *
1197         * @param attributes the BasicAttributes or AttributesImpl instance to convert
1198         * @param registries The registries, needed ro build a ServerEntry
1199         * @param dn The DN which is needed by the ServerEntry 
1200         * @return An instance of a ServerEntry object
1201         * 
1202         * @throws InvalidAttributeIdentifierException If we get an invalid attribute
1203         */
1204        public static Entry toClientEntry( Attributes attributes, DN dn ) throws LdapException
1205        {
1206            if ( attributes instanceof BasicAttributes )
1207            {
1208                try
1209                {
1210                    Entry entry = new DefaultClientEntry( dn );
1211    
1212                    for ( NamingEnumeration<? extends Attribute> attrs = attributes.getAll(); attrs.hasMoreElements(); )
1213                    {
1214                        Attribute attr = attrs.nextElement();
1215    
1216                        EntryAttribute entryAttribute = toClientAttribute( attr );
1217    
1218                        if ( entryAttribute != null )
1219                        {
1220                            entry.put( entryAttribute );
1221                        }
1222                    }
1223    
1224                    return entry;
1225                }
1226                catch ( LdapException ne )
1227                {
1228                    throw new LdapInvalidAttributeTypeException( ne.getMessage() );
1229                }
1230            }
1231            else
1232            {
1233                return null;
1234            }
1235        }
1236    
1237    
1238        /**
1239         * Converts an {@link Entry} to an {@link Attributes}.
1240         *
1241         * @param entry
1242         *      the {@link Entry} to convert
1243         * @return
1244         *      the equivalent {@link Attributes}
1245         */
1246        public static Attributes toAttributes( Entry entry )
1247        {
1248            if ( entry != null )
1249            {
1250                Attributes attributes = new BasicAttributes( true );
1251    
1252                // Looping on attributes
1253                for ( Iterator<EntryAttribute> attributeIterator = entry.iterator(); attributeIterator.hasNext(); )
1254                {
1255                    EntryAttribute entryAttribute = ( EntryAttribute ) attributeIterator.next();
1256    
1257                    attributes.put( toAttribute( entryAttribute ) );
1258                }
1259    
1260                return attributes;
1261            }
1262    
1263            return null;
1264        }
1265    
1266    
1267        /**
1268         * Converts an {@link EntryAttribute} to an {@link Attribute}.
1269         *
1270         * @param entryAttribute
1271         *      the {@link EntryAttribute} to convert
1272         * @return
1273         *      the equivalent {@link Attribute}
1274         */
1275        public static Attribute toAttribute( EntryAttribute entryAttribute )
1276        {
1277            if ( entryAttribute != null )
1278            {
1279                Attribute attribute = new BasicAttribute( entryAttribute.getId() );
1280    
1281                // Looping on values
1282                for ( Iterator<Value<?>> valueIterator = entryAttribute.iterator(); valueIterator.hasNext(); )
1283                {
1284                    Value<?> value = valueIterator.next();
1285                    attribute.add( value.get() );
1286                }
1287    
1288                return attribute;
1289            }
1290    
1291            return null;
1292        }
1293    
1294    
1295        /**
1296         * Convert a BasicAttribute or a AttributeImpl to a EntryAttribute
1297         *
1298         * @param attribute the BasicAttributes or AttributesImpl instance to convert
1299         * @param attributeType
1300         * @return An instance of a ClientEntry object
1301         * 
1302         * @throws InvalidAttributeIdentifierException If we had an incorrect attribute
1303         */
1304        public static EntryAttribute toClientAttribute( Attribute attribute )
1305        {
1306            if ( attribute == null )
1307            {
1308                return null;
1309            }
1310    
1311            try
1312            {
1313                EntryAttribute clientAttribute = new DefaultClientAttribute( attribute.getID() );
1314    
1315                for ( NamingEnumeration<?> values = attribute.getAll(); values.hasMoreElements(); )
1316                {
1317                    Object value = values.nextElement();
1318    
1319                    if ( value instanceof String )
1320                    {
1321                        clientAttribute.add( ( String ) value );
1322                    }
1323                    else if ( value instanceof byte[] )
1324                    {
1325                        clientAttribute.add( ( byte[] ) value );
1326                    }
1327                    else
1328                    {
1329                        clientAttribute.add( ( String ) null );
1330                    }
1331                }
1332    
1333                return clientAttribute;
1334            }
1335            catch ( NamingException ne )
1336            {
1337                return null;
1338            }
1339        }
1340    }