001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *  
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *  
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License. 
018 *  
019 */
020package org.apache.directory.api.ldap.model.ldif;
021
022
023import java.io.Externalizable;
024import java.io.IOException;
025import java.io.ObjectInput;
026import java.io.ObjectOutput;
027import java.util.HashMap;
028import java.util.Iterator;
029import java.util.LinkedList;
030import java.util.List;
031import java.util.Map;
032import java.util.concurrent.ConcurrentHashMap;
033
034import org.apache.directory.api.i18n.I18n;
035import org.apache.directory.api.ldap.model.entry.Attribute;
036import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
037import org.apache.directory.api.ldap.model.entry.DefaultEntry;
038import org.apache.directory.api.ldap.model.entry.DefaultModification;
039import org.apache.directory.api.ldap.model.entry.Entry;
040import org.apache.directory.api.ldap.model.entry.Modification;
041import org.apache.directory.api.ldap.model.entry.ModificationOperation;
042import org.apache.directory.api.ldap.model.entry.StringValue;
043import org.apache.directory.api.ldap.model.entry.Value;
044import org.apache.directory.api.ldap.model.exception.LdapException;
045import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
046import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
047import org.apache.directory.api.ldap.model.message.Control;
048import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
049import org.apache.directory.api.ldap.model.name.Dn;
050import org.apache.directory.api.ldap.model.name.Rdn;
051import org.apache.directory.api.ldap.model.schema.SchemaManager;
052import org.apache.directory.api.util.Base64;
053import org.apache.directory.api.util.Strings;
054
055
056/**
057 * A entry to be populated by an ldif parser.
058 * 
059 * We will have different kind of entries : 
060 * <ul>
061 * <li>added entries</li>
062 * <li>deleted entries</li>
063 * <li>modified entries</li>
064 * <li>Rdn modified entries</li>
065 * <li>Dn modified entries</li>
066 * </ul>
067 * 
068 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
069 */
070public class LdifEntry implements Cloneable, Externalizable, Iterable<Attribute>
071{
072    /** Used in toArray() */
073    public static final Modification[] EMPTY_MODS = new Modification[0];
074
075    /** the change type */
076    private ChangeType changeType;
077
078    /** the modification item list */
079    private List<Modification> modificationList;
080
081    /** The map containing all the modifications */
082    private Map<String, Modification> modifications;
083
084    /** The new superior */
085    private String newSuperior;
086
087    /** The new rdn */
088    private String newRdn;
089
090    /** The delete old rdn flag */
091    private boolean deleteOldRdn;
092
093    /** the entry */
094    private Entry entry;
095
096    /** the DN */
097    private Dn entryDn;
098
099    /** The controls */
100    private Map<String, LdifControl> controls;
101
102    /** The lengthBeforeParsing of the entry at the time of parsing. This includes
103     *  the lengthBeforeParsing of the comments present in entry at the time of parsing
104     *  so this lengthBeforeParsing may not always match with the lengthBeforeParsing of the entry
105     *  data present in memory.
106     */
107    private int lengthBeforeParsing = 0;
108
109    /** the position of the entry in the file or given input string*/
110    private long offset = 0;
111
112
113    /**
114     * Creates a new LdifEntry object.
115     */
116    public LdifEntry()
117    {
118        // Default LDIF content
119        changeType = ChangeType.None;
120        modificationList = new LinkedList<>();
121        modifications = new HashMap<>();
122        entry = new DefaultEntry( ( Dn ) null );
123        entryDn = null;
124        controls = null;
125    }
126
127
128    /**
129     * Creates a new schema aware LdifEntry object.
130     * 
131     * @param schemaManager The SchemaManager
132     */
133    public LdifEntry( SchemaManager schemaManager )
134    {
135        // Default LDIF content
136        changeType = ChangeType.None;
137        modificationList = new LinkedList<>();
138        modifications = new HashMap<>();
139        entry = new DefaultEntry( schemaManager, ( Dn ) null );
140        entryDn = null;
141        controls = null;
142    }
143
144
145    /**
146     * Creates a new LdifEntry object, storing an Entry
147     * 
148     * @param entry The entry to encapsulate
149     */
150    public LdifEntry( Entry entry )
151    {
152        // Default LDIF content
153        changeType = ChangeType.None;
154        modificationList = new LinkedList<>();
155        modifications = new HashMap<>();
156        this.entry = entry;
157        entryDn = entry.getDn();
158        controls = null;
159    }
160
161
162    /**
163     * Creates a LdifEntry using a list of strings representing the Ldif element
164     * 
165     * @param dn The LdifEntry DN
166     * @param avas The Ldif to convert to an LdifEntry
167     * @throws LdapInvalidAttributeValueException If either the AttributeType or the associated value
168     * is incorrect
169     * @throws LdapLdifException If we get any other exception
170     */
171    public LdifEntry( Dn dn, Object... avas ) throws LdapInvalidAttributeValueException, LdapLdifException
172    {
173        // First, convert the arguments to a full LDIF
174        StringBuilder sb = new StringBuilder();
175        int pos = 0;
176        boolean valueExpected = false;
177        String dnStr = null;
178
179        if ( dn == null )
180        {
181            dnStr = "";
182        }
183        else
184        {
185            dnStr = dn.getName();
186        }
187
188        if ( LdifUtils.isLDIFSafe( dnStr ) )
189        {
190            sb.append( "dn: " ).append( dnStr ).append( '\n' );
191        }
192        else
193        {
194            sb.append( "dn:: " ).append( Base64.encode( Strings.getBytesUtf8( dnStr ) ) ).append( '\n' );
195        }
196
197        for ( Object ava : avas )
198        {
199            if ( !valueExpected )
200            {
201                if ( !( ava instanceof String ) )
202                {
203                    throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n.err(
204                        I18n.ERR_12085, pos + 1 ) );
205                }
206
207                String attribute = ( String ) ava;
208                sb.append( attribute );
209
210                if ( attribute.indexOf( ':' ) != -1 )
211                {
212                    sb.append( '\n' );
213                }
214                else
215                {
216                    valueExpected = true;
217                }
218            }
219            else
220            {
221                if ( ava instanceof String )
222                {
223                    sb.append( ": " ).append( ( String ) ava ).append( '\n' );
224                }
225                else if ( ava instanceof byte[] )
226                {
227                    sb.append( ":: " );
228                    sb.append( new String( Base64.encode( ( byte[] ) ava ) ) );
229                    sb.append( '\n' );
230                }
231                else
232                {
233                    throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n.err(
234                        I18n.ERR_12086, pos + 1 ) );
235                }
236
237                valueExpected = false;
238            }
239        }
240
241        if ( valueExpected )
242        {
243            throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n
244                .err( I18n.ERR_12087 ) );
245        }
246
247        // Now, parse the Ldif and convert it to a LdifEntry
248        LdifReader reader = new LdifReader();
249        List<LdifEntry> ldifEntries = reader.parseLdif( sb.toString() );
250
251        try
252        {
253            reader.close();
254        }
255        catch ( IOException e )
256        {
257            e.printStackTrace();
258        }
259
260        if ( ( ldifEntries != null ) && ( ldifEntries.size() == 1 ) )
261        {
262            LdifEntry ldifEntry = ldifEntries.get( 0 );
263
264            changeType = ldifEntry.getChangeType();
265            controls = ldifEntry.getControls();
266            entryDn = ldifEntry.getDn();
267
268            switch ( ldifEntry.getChangeType() )
269            {
270                case Add:
271                    // Fallback
272                case None:
273                    entry = ldifEntry.getEntry();
274                    break;
275
276                case Delete:
277                    break;
278
279                case ModDn:
280                case ModRdn:
281                    newRdn = ldifEntry.getNewRdn();
282                    newSuperior = ldifEntry.getNewSuperior();
283                    deleteOldRdn = ldifEntry.isDeleteOldRdn();
284                    break;
285
286                case Modify:
287                    modificationList = ldifEntry.getModifications();
288                    modifications = new HashMap<>();
289
290                    for ( Modification modification : modificationList )
291                    {
292                        modifications.put( modification.getAttribute().getId(), modification );
293                    }
294
295                    break;
296
297                default:
298                    throw new IllegalArgumentException( "Unexpected ChangeType: " + changeType );
299            }
300        }
301    }
302
303
304    /**
305     * Creates a LdifEntry using a list of strings representing the Ldif element
306     * 
307     * @param dn The LdifEntry DN
308     * @param strings The Ldif attributes and values to convert to an LdifEntry
309     * @throws LdapInvalidDnException If the Dn is invalid
310     * @throws LdapInvalidAttributeValueException If either the AttributeType or the associated value
311     * is incorrect
312     * @throws LdapLdifException If we get any other exception
313     */
314    public LdifEntry( String dn, Object... strings )
315        throws LdapInvalidAttributeValueException, LdapLdifException, LdapInvalidDnException
316    {
317        this( new Dn( dn ), strings );
318    }
319
320
321    /**
322     * Set the Distinguished Name
323     * 
324     * @param dn The Distinguished Name
325     */
326    public void setDn( Dn dn )
327    {
328        entryDn = dn;
329        entry.setDn( dn );
330    }
331
332
333    /**
334     * Set the Distinguished Name
335     * 
336     * @param dn The Distinguished Name
337     * @throws LdapInvalidDnException If the Dn is invalid
338     */
339    public void setDn( String dn ) throws LdapInvalidDnException
340    {
341        entryDn = new Dn( dn );
342        entry.setDn( entryDn );
343    }
344
345
346    /**
347     * Set the modification type
348     * 
349     * @param changeType The change type
350     * 
351     */
352    public void setChangeType( ChangeType changeType )
353    {
354        this.changeType = changeType;
355    }
356
357
358    /**
359     * Set the change type
360     * 
361     * @param changeType The change type
362     */
363    public void setChangeType( String changeType )
364    {
365        if ( "add".equals( changeType ) )
366        {
367            this.changeType = ChangeType.Add;
368        }
369        else if ( "modify".equals( changeType ) )
370        {
371            this.changeType = ChangeType.Modify;
372        }
373        else if ( "moddn".equals( changeType ) )
374        {
375            this.changeType = ChangeType.ModDn;
376        }
377        else if ( "modrdn".equals( changeType ) )
378        {
379            this.changeType = ChangeType.ModRdn;
380        }
381        else if ( "delete".equals( changeType ) )
382        {
383            this.changeType = ChangeType.Delete;
384        }
385    }
386
387
388    /**
389     * Add a modification item (used by modify operations)
390     * 
391     * @param modification The modification to be added
392     */
393    public void addModification( Modification modification )
394    {
395        if ( changeType == ChangeType.Modify )
396        {
397            modificationList.add( modification );
398            modifications.put( modification.getAttribute().getId(), modification );
399        }
400    }
401
402
403    /**
404     * Add a modification item (used by modify operations)
405     * 
406     * @param modOp The operation. One of : 
407     * <ul>
408     * <li>ModificationOperation.ADD_ATTRIBUTE</li>
409     * <li>ModificationOperation.REMOVE_ATTRIBUTE</li>
410     * <li>ModificationOperation.REPLACE_ATTRIBUTE</li>
411     * </ul>
412     * 
413     * @param attr The attribute to be added
414     */
415    public void addModification( ModificationOperation modOp, Attribute attr )
416    {
417        if ( changeType == ChangeType.Modify )
418        {
419            Modification item = new DefaultModification( modOp, attr );
420            modificationList.add( item );
421            modifications.put( attr.getId(), item );
422        }
423    }
424
425
426    /**
427     * Add a modification with no value
428     * 
429     * @param modOp The modification operation value. One of : 
430     * <ul>
431     * <li>ModificationOperation.ADD_ATTRIBUTE</li>
432     * <li>ModificationOperation.REMOVE_ATTRIBUTE</li>
433     * <li>ModificationOperation.REPLACE_ATTRIBUTE</li>
434     * </ul>
435     * 
436     * @param id The attribute's ID
437     */
438    public void addModification( ModificationOperation modOp, String id )
439    {
440        if ( changeType == ChangeType.Modify )
441        {
442            Attribute attr = new DefaultAttribute( id );
443
444            Modification item = new DefaultModification( modOp, attr );
445            modificationList.add( item );
446            modifications.put( id, item );
447        }
448    }
449
450
451    /**
452     * Add a modification
453     * 
454     * @param modOp The modification operation value. One of : 
455     * <ul>
456     * <li>ModificationOperation.ADD_ATTRIBUTE</li>
457     * <li>ModificationOperation.REMOVE_ATTRIBUTE</li>
458     * <li>ModificationOperation.REPLACE_ATTRIBUTE</li>
459     * </ul>
460     * 
461     * @param id The attribute's ID
462     * @param value The attribute's value
463     */
464    public void addModification( ModificationOperation modOp, String id, Object value )
465    {
466        if ( changeType == ChangeType.Modify )
467        {
468            Attribute attr;
469
470            if ( value == null )
471            {
472                value = new StringValue( ( String ) null );
473                attr = new DefaultAttribute( id, ( Value<?> ) value );
474            }
475            else
476            {
477                attr = ( Attribute ) value;
478            }
479
480            Modification item = new DefaultModification( modOp, attr );
481            modificationList.add( item );
482            modifications.put( id, item );
483        }
484    }
485
486
487    /**
488     * Add an attribute to the entry
489     * 
490     * @param attr The attribute to be added
491     * @throws org.apache.directory.api.ldap.model.exception.LdapException if something went wrong
492     */
493    public void addAttribute( Attribute attr ) throws LdapException
494    {
495        entry.put( attr );
496    }
497
498
499    /**
500     * Add an attribute to the entry
501     * 
502     * @param id The attribute ID
503     * 
504     * @param values The attribute values
505     * @throws LdapException if something went wrong
506     */
507    public void addAttribute( String id, Object... values ) throws LdapException
508    {
509        Attribute attribute = entry.get( id );
510        Boolean isHR = null;
511
512        if ( attribute != null )
513        {
514            isHR = attribute.isHumanReadable();
515        }
516
517        if ( values != null )
518        {
519            for ( Object value : values )
520            {
521                if ( value instanceof String )
522                {
523                    if ( isHR != null )
524                    {
525                        if ( isHR )
526                        {
527                            entry.add( id, ( String ) value );
528                        }
529                        else
530                        {
531                            entry.add( id, Strings.getBytesUtf8( ( String ) value ) );
532                        }
533                    }
534                    else
535                    {
536                        entry.add( id, ( String ) value );
537                    }
538                }
539                else
540                {
541                    if ( isHR != null )
542                    {
543                        if ( isHR )
544                        {
545                            entry.add( id, Strings.utf8ToString( ( byte[] ) value ) );
546                        }
547                        else
548                        {
549                            entry.add( id, ( byte[] ) value );
550                        }
551                    }
552                    else
553                    {
554                        entry.add( id, ( byte[] ) value );
555                    }
556                }
557            }
558        }
559        else
560        {
561            entry.add( id, ( Value<?> ) null );
562        }
563    }
564
565
566    /**
567     * Remove a list of Attributes from the LdifEntry
568     *
569     * @param ids The Attributes to remove
570     */
571    public void removeAttribute( String... ids )
572    {
573        if ( entry.containsAttribute( ids ) )
574        {
575            entry.removeAttributes( ids );
576        }
577    }
578
579
580    /**
581     * Add an attribute value to an existing attribute
582     * 
583     * @param id The attribute ID
584     * 
585     * @param value The attribute value
586     * @throws org.apache.directory.api.ldap.model.exception.LdapException if something went wrong
587     */
588    public void putAttribute( String id, Object value ) throws LdapException
589    {
590        if ( value instanceof String )
591        {
592            entry.add( id, ( String ) value );
593        }
594        else
595        {
596            entry.add( id, ( byte[] ) value );
597        }
598    }
599
600
601    /**
602     * Get the change type
603     * 
604     * @return The change type. One of : 
605     * <ul>
606     * <li>ADD</li>
607     * <li>MODIFY</li>
608     * <li>MODDN</li>
609     * <li>MODRDN</li>
610     * <li>DELETE</li>
611     * <li>NONE</li>
612     * </ul>
613     */
614    public ChangeType getChangeType()
615    {
616        return changeType;
617    }
618
619
620    /**
621     * @return The list of modification items
622     */
623    public List<Modification> getModifications()
624    {
625        return modificationList;
626    }
627
628
629    /**
630     * Gets the modification items as an array.
631     *
632     * @return modification items as an array.
633     */
634    public Modification[] getModificationArray()
635    {
636        return modificationList.toArray( EMPTY_MODS );
637    }
638
639
640    /**
641     * @return The entry Distinguished name
642     */
643    public Dn getDn()
644    {
645        return entryDn;
646    }
647
648
649    /**
650     * @return The number of entry modifications
651     */
652    public int size()
653    {
654        return modificationList.size();
655    }
656
657
658    /**
659     * Returns a attribute given it's id
660     * 
661     * @param attributeId The attribute Id
662     * @return The attribute if it exists
663     */
664    public Attribute get( String attributeId )
665    {
666        if ( "dn".equalsIgnoreCase( attributeId ) )
667        {
668            return new DefaultAttribute( "dn", entry.getDn().getName() );
669        }
670
671        return entry.get( attributeId );
672    }
673
674
675    /**
676     * Get the entry's entry
677     * 
678     * @return the stored Entry
679     */
680    public Entry getEntry()
681    {
682        if ( isEntry() )
683        {
684            return entry;
685        }
686        else
687        {
688            return null;
689        }
690    }
691
692
693    /**
694     * @return True, if the old Rdn should be deleted.
695     */
696    public boolean isDeleteOldRdn()
697    {
698        return deleteOldRdn;
699    }
700
701
702    /**
703     * Set the deleteOldRdn flag
704     * 
705     * @param deleteOldRdn True if the old Rdn should be deleted
706     */
707    public void setDeleteOldRdn( boolean deleteOldRdn )
708    {
709        this.deleteOldRdn = deleteOldRdn;
710    }
711
712
713    /**
714     * @return The new Rdn
715     */
716    public String getNewRdn()
717    {
718        return newRdn;
719    }
720
721
722    /**
723     * Set the new Rdn
724     * 
725     * @param newRdn The new Rdn
726     */
727    public void setNewRdn( String newRdn )
728    {
729        this.newRdn = newRdn;
730    }
731
732
733    /**
734     * @return The new superior
735     */
736    public String getNewSuperior()
737    {
738        return newSuperior;
739    }
740
741
742    /**
743     * Set the new superior
744     * 
745     * @param newSuperior The new Superior
746     */
747    public void setNewSuperior( String newSuperior )
748    {
749        this.newSuperior = newSuperior;
750    }
751
752
753    /**
754     * @return True if this is a content ldif
755     */
756    public boolean isLdifContent()
757    {
758        return changeType == ChangeType.None;
759    }
760
761
762    /**
763     * @return True if there is this is a change ldif
764     */
765    public boolean isLdifChange()
766    {
767        return changeType != ChangeType.None;
768    }
769
770
771    /**
772     * @return True if the entry is an ADD entry
773     */
774    public boolean isChangeAdd()
775    {
776        return changeType == ChangeType.Add;
777    }
778
779
780    /**
781     * @return True if the entry is a DELETE entry
782     */
783    public boolean isChangeDelete()
784    {
785        return changeType == ChangeType.Delete;
786    }
787
788
789    /**
790     * @return True if the entry is a MODDN entry
791     */
792    public boolean isChangeModDn()
793    {
794        return changeType == ChangeType.ModDn;
795    }
796
797
798    /**
799     * @return True if the entry is a MODRDN entry
800     */
801    public boolean isChangeModRdn()
802    {
803        return changeType == ChangeType.ModRdn;
804    }
805
806
807    /**
808     * @return True if the entry is a MODIFY entry
809     */
810    public boolean isChangeModify()
811    {
812        return changeType == ChangeType.Modify;
813    }
814
815
816    /**
817     * Tells if the current entry is a added one
818     *
819     * @return <code>true</code> if the entry is added
820     */
821    public boolean isEntry()
822    {
823        return ( changeType == ChangeType.None ) || ( changeType == ChangeType.Add );
824    }
825
826
827    /**
828     * @return true if the entry has some controls
829     */
830    public boolean hasControls()
831    {
832        return controls != null;
833    }
834
835
836    /**
837     * @return The set of controls for this entry
838     */
839    public Map<String, LdifControl> getControls()
840    {
841        return controls;
842    }
843
844
845    /**
846     * @param oid The control's OID
847     * @return The associated control, if any
848     */
849    public LdifControl getControl( String oid )
850    {
851        if ( controls != null )
852        {
853            return controls.get( oid );
854        }
855
856        return null;
857    }
858
859
860    /**
861     * Add a control to the entry
862     * 
863     * @param controls The added controls
864     */
865    public void addControl( Control... controls )
866    {
867        if ( controls == null )
868        {
869            throw new IllegalArgumentException( "The added control must not be null" );
870        }
871
872        for ( Control control : controls )
873        {
874            if ( changeType == ChangeType.None )
875            {
876                changeType = ChangeType.Add;
877            }
878
879            if ( this.controls == null )
880            {
881                this.controls = new ConcurrentHashMap<>();
882            }
883
884            if ( control instanceof LdifControl )
885            {
886                this.controls.put( control.getOid(), ( LdifControl ) control );
887            }
888            else
889            {
890                LdifControl ldifControl = new LdifControl( control.getOid() );
891                ldifControl.setCritical( control.isCritical() );
892                this.controls.put( control.getOid(), new LdifControl( control.getOid() ) );
893            }
894        }
895    }
896
897
898    /**
899     * Clone method
900     * @return a clone of the current instance
901     * @exception CloneNotSupportedException If there is some problem while cloning the instance
902     */
903    @Override
904    public LdifEntry clone() throws CloneNotSupportedException
905    {
906        LdifEntry clone = ( LdifEntry ) super.clone();
907
908        if ( modificationList != null )
909        {
910            for ( Modification modif : modificationList )
911            {
912                Modification modifClone = new DefaultModification( modif.getOperation(),
913                    modif.getAttribute().clone() );
914                clone.modificationList.add( modifClone );
915            }
916        }
917
918        if ( modifications != null )
919        {
920            for ( Map.Entry<String, Modification> mapEntry : modifications.entrySet() )
921            {
922                Modification modif = mapEntry.getValue();
923                Modification modifClone = new DefaultModification( modif.getOperation(),
924                    modif.getAttribute().clone() );
925                clone.modifications.put( mapEntry.getKey(), modifClone );
926            }
927
928        }
929
930        if ( entry != null )
931        {
932            clone.entry = entry.clone();
933        }
934
935        return clone;
936    }
937
938
939    /** 
940     *  Returns the lengthBeforeParsing of the entry at the time of parsing. This includes
941     *  the lengthBeforeParsing of the comments present in entry at the time of parsing
942     *  so this lengthBeforeParsing may not always match with the lengthBeforeParsing of the entry
943     *  data present in memory.
944     *  
945     *  @return The entry length, comments included 
946     */
947    public int getLengthBeforeParsing()
948    {
949        return lengthBeforeParsing;
950    }
951
952
953    /**
954     * @param lengthBeforeParsing the lengthBeforeParsing to set
955     */
956    /**No qualifier*/
957    void setLengthBeforeParsing( int length )
958    {
959        this.lengthBeforeParsing = length;
960    }
961
962
963    /**
964     * @return the offset
965     */
966    public long getOffset()
967    {
968        return offset;
969    }
970
971
972    /**
973     * @param offset the offset to set
974     */
975    /**No qualifier*/
976    void setOffset( long offset )
977    {
978        this.offset = offset;
979    }
980
981
982    /**
983     * Returns an enumeration containing the zero or more attributes in the
984     * collection. The behavior of the enumeration is not specified if the
985     * attribute collection is changed.
986     *
987     * @return an enumeration of all contained attributes
988     */
989    @Override
990    public Iterator<Attribute> iterator()
991    {
992        return entry.iterator();
993    }
994
995
996    /**
997     * @return a String representing the Entry, as a LDIF 
998     */
999    @Override
1000    public String toString()
1001    {
1002        try
1003        {
1004            return LdifUtils.convertToLdif( this );
1005        }
1006        catch ( LdapException ne )
1007        {
1008            return "";
1009        }
1010    }
1011
1012
1013    /**
1014     * @see Object#hashCode()
1015     * 
1016     * @return the instance's hash code
1017     */
1018    @Override
1019    public int hashCode()
1020    {
1021        int result = 37;
1022
1023        if ( entry != null && entry.getDn() != null )
1024        {
1025            result = result * 17 + entry.getDn().hashCode();
1026        }
1027
1028        if ( changeType != null )
1029        {
1030            result = result * 17 + changeType.hashCode();
1031
1032            // Check each different cases
1033            switch ( changeType )
1034            {
1035                case None:
1036                    // Fall through
1037                case Add:
1038                    // Checks the attributes
1039                    if ( entry != null )
1040                    {
1041                        result = result * 17 + entry.hashCode();
1042                    }
1043
1044                    break;
1045
1046                case Delete:
1047                    // Nothing to compute
1048                    break;
1049
1050                case Modify:
1051                    if ( modificationList != null )
1052                    {
1053                        result = result * 17 + modificationList.hashCode();
1054
1055                        for ( Modification modification : modificationList )
1056                        {
1057                            result = result * 17 + modification.hashCode();
1058                        }
1059                    }
1060
1061                    break;
1062
1063                case ModDn:
1064                case ModRdn:
1065                    result = result * 17;
1066
1067                    if ( deleteOldRdn )
1068                    {
1069                        result++;
1070                    }
1071                    else
1072                    {
1073                        result--;
1074                    }
1075
1076                    if ( newRdn != null )
1077                    {
1078                        result = result * 17 + newRdn.hashCode();
1079                    }
1080
1081                    if ( newSuperior != null )
1082                    {
1083                        result = result * 17 + newSuperior.hashCode();
1084                    }
1085
1086                    break;
1087
1088                default:
1089                    // do nothing
1090                    break;
1091            }
1092        }
1093
1094        if ( controls != null )
1095        {
1096            for ( String control : controls.keySet() )
1097            {
1098                result = result * 17 + control.hashCode();
1099            }
1100        }
1101
1102        return result;
1103    }
1104
1105
1106    /**
1107     * {@inheritDoc}
1108     */
1109    @Override
1110    public boolean equals( Object o )
1111    {
1112        // Basic equals checks
1113        if ( this == o )
1114        {
1115            return true;
1116        }
1117
1118        if ( o == null )
1119        {
1120            return false;
1121        }
1122
1123        if ( !( o instanceof LdifEntry ) )
1124        {
1125            return false;
1126        }
1127
1128        LdifEntry otherEntry = ( LdifEntry ) o;
1129
1130        // Check the Dn
1131        Dn thisDn = entryDn;
1132        Dn dnEntry = otherEntry.getDn();
1133
1134        if ( !thisDn.equals( dnEntry ) )
1135        {
1136            return false;
1137        }
1138
1139        // Check the changeType
1140        if ( changeType != otherEntry.changeType )
1141        {
1142            return false;
1143        }
1144
1145        // Check each different cases
1146        switch ( changeType )
1147        {
1148            case None:
1149                // Fall through
1150            case Add:
1151                // Checks the attributes
1152                if ( entry.size() != otherEntry.entry.size() )
1153                {
1154                    return false;
1155                }
1156
1157                if ( !entry.equals( otherEntry.entry ) )
1158                {
1159                    return false;
1160                }
1161
1162                break;
1163
1164            case Delete:
1165                // Nothing to do, if the DNs are equals
1166                break;
1167
1168            case Modify:
1169                // Check the modificationItems list
1170
1171                // First, deal with special cases
1172                if ( modificationList == null )
1173                {
1174                    if ( otherEntry.modificationList != null )
1175                    {
1176                        return false;
1177                    }
1178                    else
1179                    {
1180                        break;
1181                    }
1182                }
1183
1184                if ( otherEntry.modificationList == null )
1185                {
1186                    return false;
1187                }
1188
1189                if ( modificationList.size() != otherEntry.modificationList.size() )
1190                {
1191                    return false;
1192                }
1193
1194                // Now, compares the contents
1195                int i = 0;
1196
1197                for ( Modification modification : modificationList )
1198                {
1199                    if ( !modification.equals( otherEntry.modificationList.get( i ) ) )
1200                    {
1201                        return false;
1202                    }
1203
1204                    i++;
1205                }
1206
1207                break;
1208
1209            case ModDn:
1210            case ModRdn:
1211                // Check the deleteOldRdn flag
1212                if ( deleteOldRdn != otherEntry.deleteOldRdn )
1213                {
1214                    return false;
1215                }
1216
1217                // Check the newRdn value
1218                try
1219                {
1220                    Rdn thisNewRdn = new Rdn( newRdn );
1221                    Rdn entryNewRdn = new Rdn( otherEntry.newRdn );
1222
1223                    if ( !thisNewRdn.equals( entryNewRdn ) )
1224                    {
1225                        return false;
1226                    }
1227                }
1228                catch ( LdapInvalidDnException ine )
1229                {
1230                    return false;
1231                }
1232
1233                // Check the newSuperior value
1234                try
1235                {
1236                    Dn thisNewSuperior = new Dn( newSuperior );
1237                    Dn entryNewSuperior = new Dn( otherEntry.newSuperior );
1238
1239                    if ( !thisNewSuperior.equals( entryNewSuperior ) )
1240                    {
1241                        return false;
1242                    }
1243                }
1244                catch ( LdapInvalidDnException ine )
1245                {
1246                    return false;
1247                }
1248
1249                break;
1250
1251            default:
1252                // do nothing
1253                break;
1254        }
1255
1256        if ( controls != null )
1257        {
1258            Map<String, LdifControl> otherControls = otherEntry.controls;
1259
1260            if ( otherControls == null )
1261            {
1262                return false;
1263            }
1264
1265            if ( controls.size() != otherControls.size() )
1266            {
1267                return false;
1268            }
1269
1270            for ( Map.Entry<String, LdifControl> mapEntry : controls.entrySet() )
1271            {
1272                String controlOid = mapEntry.getKey();
1273
1274                if ( !otherControls.containsKey( controlOid ) )
1275                {
1276                    return false;
1277                }
1278
1279                Control thisControl = mapEntry.getValue();
1280                Control otherControl = otherControls.get( controlOid );
1281
1282                if ( thisControl == null )
1283                {
1284                    if ( otherControl != null )
1285                    {
1286                        return false;
1287                    }
1288                }
1289                else
1290                {
1291                    if ( !thisControl.equals( otherControl ) )
1292                    {
1293                        return false;
1294                    }
1295                }
1296            }
1297
1298            return true;
1299        }
1300        else
1301        {
1302            return otherEntry.controls == null;
1303        }
1304    }
1305
1306
1307    /**
1308     * @see Externalizable#readExternal(ObjectInput)
1309     * 
1310     * @param in The stream from which the LdifEntry is read
1311     * @throws IOException If the stream can't be read
1312     * @throws ClassNotFoundException If the LdifEntry can't be created 
1313     */
1314    @Override
1315    public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
1316    {
1317        // Read the changeType
1318        int type = in.readInt();
1319        changeType = ChangeType.getChangeType( type );
1320
1321        // Read the modification
1322        switch ( changeType )
1323        {
1324            case Add:
1325            case None:
1326                // Read the entry
1327                entry.readExternal( in );
1328                entryDn = entry.getDn();
1329
1330                break;
1331
1332            case Delete:
1333                // Read the Dn
1334                entryDn = new Dn();
1335                entryDn.readExternal( in );
1336
1337                break;
1338
1339            case ModDn:
1340                // Fallback
1341            case ModRdn:
1342                // Read the Dn
1343                entryDn = new Dn();
1344                entryDn.readExternal( in );
1345
1346                deleteOldRdn = in.readBoolean();
1347
1348                if ( in.readBoolean() )
1349                {
1350                    newRdn = in.readUTF();
1351                }
1352
1353                if ( in.readBoolean() )
1354                {
1355                    newSuperior = in.readUTF();
1356                }
1357
1358                break;
1359
1360            case Modify:
1361                // Read the Dn
1362                entryDn = new Dn();
1363                entryDn.readExternal( in );
1364
1365                // Read the modifications
1366                int nbModifs = in.readInt();
1367
1368                for ( int i = 0; i < nbModifs; i++ )
1369                {
1370                    Modification modification = new DefaultModification();
1371                    modification.readExternal( in );
1372
1373                    addModification( modification );
1374                }
1375
1376                break;
1377
1378            default:
1379                throw new IllegalArgumentException( "Unexpected ChangeType: " + changeType );
1380        }
1381
1382        int nbControls = in.readInt();
1383
1384        // We have at least a control
1385        if ( nbControls > 0 )
1386        {
1387            controls = new ConcurrentHashMap<>( nbControls );
1388
1389            for ( int i = 0; i < nbControls; i++ )
1390            {
1391                LdifControl control = new LdifControl();
1392
1393                control.readExternal( in );
1394
1395                controls.put( control.getOid(), control );
1396            }
1397        }
1398    }
1399
1400
1401    /**
1402     * @see Externalizable#readExternal(ObjectInput)
1403     * @param out The stream in which the ChangeLogEvent will be serialized.
1404     * @throws IOException If the serialization fail
1405     */
1406    @Override
1407    public void writeExternal( ObjectOutput out ) throws IOException
1408    {
1409        // Write the changeType
1410        out.writeInt( changeType.getChangeType() );
1411
1412        // Write the data
1413        switch ( changeType )
1414        {
1415            case Add:
1416            case None:
1417                entry.writeExternal( out );
1418                break;
1419
1420            // Fallback
1421            case Delete:
1422                // we write the Dn
1423                entryDn.writeExternal( out );
1424                break;
1425
1426            case ModDn:
1427                // Fallback
1428            case ModRdn:
1429                // Write the Dn
1430                entryDn.writeExternal( out );
1431
1432                out.writeBoolean( deleteOldRdn );
1433
1434                if ( newRdn == null )
1435                {
1436                    out.writeBoolean( false );
1437                }
1438                else
1439                {
1440                    out.writeBoolean( true );
1441                    out.writeUTF( newRdn );
1442                }
1443
1444                if ( newSuperior != null )
1445                {
1446                    out.writeBoolean( true );
1447                    out.writeUTF( newSuperior );
1448                }
1449                else
1450                {
1451                    out.writeBoolean( false );
1452                }
1453                break;
1454
1455            case Modify:
1456                // Write the Dn
1457                entryDn.writeExternal( out );
1458
1459                // Write the modifications
1460                out.writeInt( modificationList.size() );
1461
1462                for ( Modification modification : modificationList )
1463                {
1464                    modification.writeExternal( out );
1465                }
1466
1467                break;
1468
1469            default:
1470                throw new IllegalArgumentException( "Unexpected ChangeType: " + changeType );
1471        }
1472
1473        // The controls
1474        if ( controls != null )
1475        {
1476            // Write the control
1477            out.writeInt( controls.size() );
1478
1479            for ( LdifControl control : controls.values() )
1480            {
1481                control.writeExternal( out );
1482            }
1483        }
1484        else
1485        {
1486            // No control, write -1
1487            out.writeInt( -1 );
1488        }
1489
1490        // and flush the result
1491        out.flush();
1492    }
1493}