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