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