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