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    
021    package org.apache.directory.shared.ldap.ldif;
022    
023    import java.io.Externalizable;
024    import java.io.IOException;
025    import java.io.ObjectInput;
026    import java.io.ObjectOutput;
027    import java.util.HashMap;
028    import java.util.LinkedList;
029    import java.util.List;
030    import java.util.Map;
031    
032    import javax.naming.InvalidNameException;
033    import javax.naming.NamingException;
034    import javax.naming.ldap.Control;
035    
036    import org.apache.directory.shared.ldap.entry.Entry;
037    import org.apache.directory.shared.ldap.entry.EntryAttribute;
038    import org.apache.directory.shared.ldap.entry.Modification;
039    import org.apache.directory.shared.ldap.entry.ModificationOperation;
040    import org.apache.directory.shared.ldap.entry.Value;
041    import org.apache.directory.shared.ldap.entry.client.ClientEntry;
042    import org.apache.directory.shared.ldap.entry.client.ClientModification;
043    import org.apache.directory.shared.ldap.entry.client.ClientStringValue;
044    import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute;
045    import org.apache.directory.shared.ldap.entry.client.DefaultClientEntry;
046    import org.apache.directory.shared.ldap.name.LdapDN;
047    import org.apache.directory.shared.ldap.name.Rdn;
048    import org.apache.directory.shared.ldap.util.StringTools;
049    
050    
051    /**
052     * A entry to be populated by an ldif parser.
053     * 
054     * We will have different kind of entries : 
055     * - added entries 
056     * - deleted entries 
057     * - modified entries 
058     * - RDN modified entries 
059     * - DN modified entries
060     * 
061     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
062     * @version $Rev$, $Date$
063     */
064    public class LdifEntry implements Cloneable, Externalizable
065    {
066        private static final long serialVersionUID = 2L;
067        
068        /** Used in toArray() */
069        public static final Modification[] EMPTY_MODS = new Modification[0];
070    
071        /** the change type */
072        private ChangeType changeType;
073    
074        /** the modification item list */
075        private List<Modification> modificationList;
076    
077        private Map<String, Modification> modificationItems;
078    
079        /** The new superior */
080        private String newSuperior;
081    
082        /** The new rdn */
083        private String newRdn;
084    
085        /** The delete old rdn flag */
086        private boolean deleteOldRdn;
087    
088        /** the entry */
089        private ClientEntry entry;
090    
091        
092        /** The control */
093        private Control control;
094    
095        /**
096         * Creates a new Entry object.
097         */
098        public LdifEntry()
099        {
100            changeType = ChangeType.Add; // Default LDIF content
101            modificationList = new LinkedList<Modification>();
102            modificationItems = new HashMap<String, Modification>();
103            entry = new DefaultClientEntry( null );
104            control = null;
105        }
106    
107        
108        /**
109         * Set the Distinguished Name
110         * 
111         * @param dn
112         *            The Distinguished Name
113         */
114        public void setDn( LdapDN dn )
115        {
116            entry.setDn( (LdapDN)dn.clone() );
117        }
118    
119        
120        /**
121         * Set the Distinguished Name
122         * 
123         * @param dn
124         *            The Distinguished Name
125         */
126        public void setDn( String dn ) throws InvalidNameException
127        {
128            LdapDN ldapDn = new LdapDN( dn );
129            entry.setDn( ldapDn );
130        }
131    
132    
133        /**
134         * Set the modification type
135         * 
136         * @param changeType
137         *            The change type
138         * 
139         */
140        public void setChangeType( ChangeType changeType )
141        {
142            this.changeType = changeType;
143        }
144    
145        /**
146         * Set the change type
147         * 
148         * @param changeType
149         *            The change type
150         */
151        public void setChangeType( String changeType )
152        {
153            if ( "add".equals( changeType ) )
154            {
155                this.changeType = ChangeType.Add;
156            }
157            else if ( "modify".equals( changeType ) )
158            {
159                this.changeType = ChangeType.Modify;
160            }
161            else if ( "moddn".equals( changeType ) )
162            {
163                this.changeType = ChangeType.ModDn;
164            }
165            else if ( "modrdn".equals( changeType ) )
166            {
167                this.changeType = ChangeType.ModRdn;
168            }
169            else if ( "delete".equals( changeType ) )
170            {
171                this.changeType = ChangeType.Delete;
172            }
173        }
174    
175        /**
176         * Add a modification item (used by modify operations)
177         * 
178         * @param modification The modification to be added
179         */
180        public void addModificationItem( Modification modification )
181        {
182            if ( changeType == ChangeType.Modify )
183            {
184                modificationList.add( modification );
185                modificationItems.put( modification.getAttribute().getId(), modification );
186            }
187        }
188    
189        /**
190         * Add a modification item (used by modify operations)
191         * 
192         * @param modOp The operation. One of : 
193         * - ModificationOperation.ADD_ATTRIBUTE
194         * - ModificationOperation.REMOVE_ATTRIBUTE 
195         * - ModificationOperation.REPLACE_ATTRIBUTE
196         * 
197         * @param attr The attribute to be added
198         */
199        public void addModificationItem( ModificationOperation modOp, EntryAttribute attr )
200        {
201            if ( changeType == ChangeType.Modify )
202            {
203                Modification item = new ClientModification( modOp, attr );
204                modificationList.add( item );
205                modificationItems.put( attr.getId(), item );
206            }
207        }
208    
209    
210        /**
211         * Add a modification item
212         * 
213         * @param modOp The operation. One of : 
214         *  - ModificationOperation.ADD_ATTRIBUTE
215         *  - ModificationOperation.REMOVE_ATTRIBUTE 
216         *  - ModificationOperation.REPLACE_ATTRIBUTE
217         * 
218         * @param modOp The modification operation value
219         * @param id The attribute's ID
220         * @param value The attribute's value
221         */
222        public void addModificationItem( ModificationOperation modOp, String id, Object value )
223        {
224            if ( changeType == ChangeType.Modify )
225            {
226                EntryAttribute attr =  null;
227                
228                if ( value == null )
229                {
230                    value = new ClientStringValue( null );
231                    attr = new DefaultClientAttribute( id, (Value<?>)value );
232                }
233                else
234                {
235                    attr = (EntryAttribute)value;
236                }
237    
238                Modification item = new ClientModification( modOp, attr );
239                modificationList.add( item );
240                modificationItems.put( id, item );
241            }
242        }
243    
244    
245        /**
246         * Add an attribute to the entry
247         * 
248         * @param attr
249         *            The attribute to be added
250         */
251        public void addAttribute( EntryAttribute attr ) throws NamingException
252        {
253            entry.put( attr );
254        }
255    
256        /**
257         * Add an attribute to the entry
258         * 
259         * @param id
260         *            The attribute ID
261         * 
262         * @param value
263         *            The attribute value
264         * 
265         */
266        public void addAttribute( String id, Object value ) throws NamingException
267        {
268            if ( value instanceof String )
269            {
270                entry.add( id, (String)value );
271            }
272            else
273            {
274                entry.add( id, (byte[])value );
275            }
276        }
277    
278        /**
279         * Add an attribute value to an existing attribute
280         * 
281         * @param id
282         *            The attribute ID
283         * 
284         * @param value
285         *            The attribute value
286         * 
287         */
288        public void putAttribute( String id, Object value ) throws NamingException
289        {
290            if ( value instanceof String )
291            {
292                entry.add( id, (String)value );
293            }
294            else
295            {
296                entry.add( id, (byte[])value );
297            }
298        }
299    
300        /**
301         * Get the change type
302         * 
303         * @return The change type. One of : ADD = 0; MODIFY = 1; MODDN = 2; MODRDN =
304         *         3; DELETE = 4;
305         */
306        public ChangeType getChangeType()
307        {
308            return changeType;
309        }
310    
311        /**
312         * @return The list of modification items
313         */
314        public List<Modification> getModificationItems()
315        {
316            return modificationList;
317        }
318    
319    
320        /**
321         * Gets the modification items as an array.
322         *
323         * @return modification items as an array.
324         */
325        public Modification[] getModificationItemsArray()
326        {
327            return modificationList.toArray( EMPTY_MODS );
328        }
329    
330    
331        /**
332         * @return The entry Distinguished name
333         */
334        public LdapDN getDn()
335        {
336            return entry.getDn();
337        }
338    
339        /**
340         * @return The number of entry modifications
341         */
342        public int size()
343        {
344            return modificationList.size();
345        }
346    
347        /**
348         * Returns a attribute given it's id
349         * 
350         * @param attributeId
351         *            The attribute Id
352         * @return The attribute if it exists
353         */
354        public EntryAttribute get( String attributeId )
355        {
356            if ( "dn".equalsIgnoreCase( attributeId ) )
357            {
358                return new DefaultClientAttribute( "dn", entry.getDn().getUpName() );
359            }
360    
361            return entry.get( attributeId );
362        }
363    
364        /**
365         * Get the entry's entry
366         * 
367         * @return the stored Entry
368         */
369        public Entry getEntry()
370        {
371            if ( isEntry() )
372            {
373                return entry;
374            }
375            else
376            {
377                return null;
378            }
379        }
380    
381        /**
382         * @return True, if the old RDN should be deleted.
383         */
384        public boolean isDeleteOldRdn()
385        {
386            return deleteOldRdn;
387        }
388    
389        /**
390         * Set the flage deleteOldRdn
391         * 
392         * @param deleteOldRdn
393         *            True if the old RDN should be deleted
394         */
395        public void setDeleteOldRdn( boolean deleteOldRdn )
396        {
397            this.deleteOldRdn = deleteOldRdn;
398        }
399    
400        /**
401         * @return The new RDN
402         */
403        public String getNewRdn()
404        {
405            return newRdn;
406        }
407    
408        /**
409         * Set the new RDN
410         * 
411         * @param newRdn
412         *            The new RDN
413         */
414        public void setNewRdn( String newRdn )
415        {
416            this.newRdn = newRdn;
417        }
418    
419        /**
420         * @return The new superior
421         */
422        public String getNewSuperior()
423        {
424            return newSuperior;
425        }
426    
427        /**
428         * Set the new superior
429         * 
430         * @param newSuperior
431         *            The new Superior
432         */
433        public void setNewSuperior( String newSuperior )
434        {
435            this.newSuperior = newSuperior;
436        }
437    
438        /**
439         * @return True if the entry is an ADD entry
440         */
441        public boolean isChangeAdd()
442        {
443            return changeType == ChangeType.Add;
444        }
445    
446        /**
447         * @return True if the entry is a DELETE entry
448         */
449        public boolean isChangeDelete()
450        {
451            return changeType == ChangeType.Delete;
452        }
453    
454        /**
455         * @return True if the entry is a MODDN entry
456         */
457        public boolean isChangeModDn()
458        {
459            return changeType == ChangeType.ModDn;
460        }
461    
462        /**
463         * @return True if the entry is a MODRDN entry
464         */
465        public boolean isChangeModRdn()
466        {
467            return changeType == ChangeType.ModRdn;
468        }
469    
470        /**
471         * @return True if the entry is a MODIFY entry
472         */
473        public boolean isChangeModify()
474        {
475            return changeType == ChangeType.Modify;
476        }
477    
478        /**
479         * Tells if the current entry is a added one
480         *
481         * @return <code>true</code> if the entry is added
482         */
483        public boolean isEntry()
484        {
485            return changeType == ChangeType.Add;
486        }
487    
488        /**
489         * @return The associated control, if any
490         */
491        public Control getControl()
492        {
493            return control;
494        }
495    
496        /**
497         * Add a control to the entry
498         * 
499         * @param control
500         *            The control
501         */
502        public void setControl( Control control )
503        {
504            this.control = control;
505        }
506    
507        /**
508         * Clone method
509         * @return a clone of the current instance
510         * @exception CloneNotSupportedException If there is some problem while cloning the instance
511         */
512        public LdifEntry clone() throws CloneNotSupportedException
513        {
514            LdifEntry clone = (LdifEntry) super.clone();
515    
516            if ( modificationList != null )
517            {
518                for ( Modification modif:modificationList )
519                {
520                    Modification modifClone = new ClientModification( modif.getOperation(), 
521                        (EntryAttribute) modif.getAttribute().clone() );
522                    clone.modificationList.add( modifClone );
523                }
524            }
525    
526            if ( modificationItems != null )
527            {
528                for ( String key:modificationItems.keySet() )
529                {
530                    Modification modif = modificationItems.get( key );
531                    Modification modifClone = new ClientModification( modif.getOperation(), 
532                        (EntryAttribute) modif.getAttribute().clone() );
533                    clone.modificationItems.put( key, modifClone );
534                }
535    
536            }
537    
538            if ( entry != null )
539            {
540                clone.entry = (ClientEntry)entry.clone();
541            }
542    
543            return clone;
544        }
545        
546        /**
547         * Dumps the attributes
548         * @return A String representing the attributes
549         */
550        private String dumpAttributes()
551        {
552            StringBuffer sb = new StringBuffer();
553            
554            for ( EntryAttribute attribute:entry )
555            {
556                if ( attribute == null )
557                {
558                    sb.append( "        Null attribute\n" );
559                    continue;
560                }
561                
562                sb.append( "        ").append( attribute.getId() ).append( ":\n" );
563                
564                for ( Value<?> value:attribute )
565                {
566                    if ( !value.isBinary() )
567                    {
568                        sb.append(  "            " ).append( value.getString() ).append('\n' );
569                    }
570                    else
571                    {
572                        sb.append(  "            " ).append( StringTools.dumpBytes( value.getBytes() ) ).append('\n' );
573                    }
574                }
575            }
576            
577            return sb.toString();
578        }
579        
580        /**
581         * Dumps the modifications
582         * @return A String representing the modifications
583         */
584        private String dumpModificationItems()
585        {
586            StringBuffer sb = new StringBuffer();
587            
588            for ( Modification modif:modificationList )
589            {
590                sb.append( "            Operation: " );
591                
592                switch ( modif.getOperation() )
593                {
594                    case ADD_ATTRIBUTE :
595                        sb.append( "ADD\n" );
596                        break;
597                        
598                    case REMOVE_ATTRIBUTE :
599                        sb.append( "REMOVE\n" );
600                        break;
601                        
602                    case REPLACE_ATTRIBUTE :
603                        sb.append( "REPLACE \n" );
604                        break;
605                        
606                    default :
607                        break; // Do nothing
608                }
609                
610                EntryAttribute attribute = modif.getAttribute();
611                
612                sb.append( "                Attribute: " ).append( attribute.getId() ).append( '\n' );
613                
614                if ( attribute.size() != 0 )
615                {
616                    for ( Value<?> value:attribute )
617                    {
618                        if ( !value.isBinary() )
619                        {
620                            sb.append(  "                " ).append( value.getString() ).append('\n' );
621                        }
622                        else
623                        {
624                            sb.append(  "                " ).append( StringTools.dumpBytes( value.getBytes() ) ).append('\n' );
625                        }
626                    }
627                }
628            }
629            
630            return sb.toString();
631        }
632    
633        
634        /**
635         * @return a String representing the Entry
636         */
637        public String toString()
638        {
639            StringBuffer sb = new StringBuffer();
640            sb.append( "Entry : " );
641            
642            if ( entry.getDn() == null )
643            {
644                sb.append( "" );
645            }
646            else
647            {
648                sb.append( entry.getDn().getUpName() ).append( '\n' );
649            }
650            
651            sb.append( '\n' );
652    
653            if ( control != null )
654            {
655                sb.append( "    Control : " ).append(  control ).append( '\n' );
656            }
657            
658            switch ( changeType )
659            {
660                case Add :
661                    sb.append( "    Change type is ADD\n" );
662                    sb.append( "        Attributes : \n" );
663                    sb.append( dumpAttributes() );
664                    break;
665                    
666                case Modify :
667                    sb.append( "    Change type is MODIFY\n" );
668                    sb.append( "        Modifications : \n" );
669                    sb.append( dumpModificationItems() );
670                    break;
671                    
672                case Delete :
673                    sb.append( "    Change type is DELETE\n" );
674                    break;
675                    
676                case ModDn :
677                case ModRdn :
678                    sb.append( "    Change type is ").append( changeType == ChangeType.ModDn ? "MODDN\n" : "MODRDN\n" );
679                    sb.append( "    Delete old RDN : " ).append( deleteOldRdn ? "true\n" : "false\n" );
680                    sb.append( "    New RDN : " ).append( newRdn ).append( '\n' );
681                    
682                    if ( !StringTools.isEmpty( newSuperior ) )
683                    {
684                        sb.append( "    New superior : " ).append( newSuperior ).append( '\n' );
685                    }
686    
687                    break;
688                    
689                default :
690                    break; // Do nothing
691            }
692            
693            return sb.toString();
694        }
695        
696        
697        /**
698         * @see Object#hashCode()
699         * 
700         * @return the instance's hash code
701         */
702        public int hashCode()
703        {
704            int result = 37;
705    
706            if ( entry.getDn() != null )
707            {
708                result = result*17 + entry.getDn().hashCode();
709            }
710            
711            if ( changeType != null )
712            {
713                result = result*17 + changeType.hashCode();
714                
715                // Check each different cases
716                switch ( changeType )
717                {
718                    case Add :
719                        // Checks the attributes
720                        if ( entry != null )
721                        {
722                            result = result * 17 + entry.hashCode();
723                        }
724                        
725                        break;
726    
727                    case Delete :
728                        // Nothing to compute
729                        break;
730                        
731                    case Modify :
732                        if ( modificationList != null )
733                        {
734                            result = result * 17 + modificationList.hashCode();
735                            
736                            for ( Modification modification:modificationList )
737                            {
738                                result = result * 17 + modification.hashCode();
739                            }
740                        }
741                        
742                        break;
743                        
744                    case ModDn :
745                    case ModRdn :
746                        result = result * 17 + ( deleteOldRdn ? 1 : -1 ); 
747                        
748                        if ( newRdn != null )
749                        {
750                            result = result*17 + newRdn.hashCode();
751                        }
752                        
753                        if ( newSuperior != null )
754                        {
755                            result = result*17 + newSuperior.hashCode();
756                        }
757                        
758                        break;
759                        
760                    default :
761                        break; // do nothing
762                }
763            }
764    
765            if ( control != null )
766            {
767                result = result * 17 + control.hashCode();
768            }
769    
770            return result;
771        }
772        
773        /**
774         * @see Object#equals(Object)
775         * @return <code>true</code> if both values are equal
776         */
777        public boolean equals( Object o )
778        {
779            // Basic equals checks
780            if ( this == o )
781            {
782                return true;
783            }
784            
785            if ( o == null )
786            {
787               return false;
788            }
789            
790            if ( ! (o instanceof LdifEntry ) )
791            {
792                return false;
793            }
794            
795            LdifEntry otherEntry = (LdifEntry)o;
796            
797            // Check the DN
798            LdapDN thisDn = entry.getDn();
799            LdapDN dnEntry = otherEntry.getDn();
800            
801            if ( !thisDn.equals( dnEntry ) )
802            {
803                return false;
804            }
805    
806            
807            // Check the changeType
808            if ( changeType != otherEntry.changeType )
809            {
810                return false;
811            }
812            
813            // Check each different cases
814            switch ( changeType )
815            {
816                case Add :
817                    // Checks the attributes
818                    if ( entry == null )
819                    {
820                        if ( otherEntry.entry != null )
821                        {
822                            return false;
823                        }
824                        else
825                        {
826                            break;
827                        }
828                    }
829                    
830                    if ( otherEntry.entry == null )
831                    {
832                        return false;
833                    }
834                    
835                    if ( entry.size() != otherEntry.entry.size() )
836                    {
837                        return false;
838                    }
839                    
840                    if ( !entry.equals( otherEntry.entry ) )
841                    {
842                        return false;
843                    }
844                    
845                    break;
846    
847                case Delete :
848                    // Nothing to do, if the DNs are equals
849                    break;
850                    
851                case Modify :
852                    // Check the modificationItems list
853    
854                    // First, deal with special cases
855                    if ( modificationList == null )
856                    {
857                        if ( otherEntry.modificationList != null )
858                        {
859                            return false;
860                        }
861                        else
862                        {
863                            break;
864                        }
865                    }
866                    
867                    if ( otherEntry.modificationList == null )
868                    {
869                        return false;
870                    }
871                    
872                    if ( modificationList.size() != otherEntry.modificationList.size() )
873                    {
874                        return false;
875                    }
876                    
877                    // Now, compares the contents
878                    int i = 0;
879                    
880                    for ( Modification modification:modificationList )
881                    {
882                        if ( ! modification.equals( otherEntry.modificationList.get( i ) ) )
883                        {
884                            return false;
885                        }
886                        
887                        i++;
888                    }
889                    
890                    break;
891                    
892                case ModDn :
893                case ModRdn :
894                    // Check the deleteOldRdn flag
895                    if ( deleteOldRdn != otherEntry.deleteOldRdn )
896                    {
897                        return false;
898                    }
899                    
900                    // Check the newRdn value
901                    try
902                    {
903                        Rdn thisNewRdn = new Rdn( newRdn );
904                        Rdn entryNewRdn = new Rdn( otherEntry.newRdn );
905    
906                        if ( !thisNewRdn.equals( entryNewRdn ) )
907                        {
908                            return false;
909                        }
910                    }
911                    catch ( InvalidNameException ine )
912                    {
913                        return false;
914                    }
915                    
916                    // Check the newSuperior value
917                    try
918                    {
919                        LdapDN thisNewSuperior = new LdapDN( newSuperior );
920                        LdapDN entryNewSuperior = new LdapDN( otherEntry.newSuperior );
921                        
922                        if ( ! thisNewSuperior.equals(  entryNewSuperior ) )
923                        {
924                            return false;
925                        }
926                    }
927                    catch ( InvalidNameException ine )
928                    {
929                        return false;
930                    }
931                    
932                    break;
933                    
934                default :
935                    break; // do nothing
936            }
937            
938            if ( control != null )
939            {
940                return control.equals( otherEntry.control );
941            }
942            else 
943            {
944                return otherEntry.control == null;
945            }
946        }
947    
948    
949        /**
950         * @see Externalizable#readExternal(ObjectInput)
951         * 
952         * @param in The stream from which the LdifEntry is read
953         * @throws IOException If the stream can't be read
954         * @throws ClassNotFoundException If the LdifEntry can't be created 
955         */
956        public void readExternal( ObjectInput in ) throws IOException , ClassNotFoundException
957        {
958            // Read the changeType
959            int type = in.readInt();
960            changeType = ChangeType.getChangeType( type );
961            entry = (ClientEntry)in.readObject();
962            
963            switch ( changeType )
964            {
965                case Add :
966                    // Fallback
967                case Delete :
968                    // we don't have anything to read, but the control
969                    break;
970    
971                case ModDn :
972                    // Fallback
973                case ModRdn :
974                    deleteOldRdn = in.readBoolean();
975                    
976                    if ( in.readBoolean() )
977                    {
978                        newRdn = in.readUTF();
979                    }
980                    
981                    if ( in.readBoolean() )
982                    {
983                        newSuperior = in.readUTF();
984                    }
985                    
986                    break;
987                    
988                case Modify :
989                    // Read the modification
990                    int nbModifs = in.readInt();
991                    
992                    
993                    for ( int i = 0; i < nbModifs; i++ )
994                    {
995                        int operation = in.readInt();
996                        String modStr = in.readUTF();
997                        DefaultClientAttribute value = (DefaultClientAttribute)in.readObject();
998                        
999                        addModificationItem( ModificationOperation.getOperation( operation ), modStr, value );
1000                    }
1001                    
1002                    break;
1003            }
1004            
1005            if ( in.available() > 0 )
1006            {
1007                // We have a control
1008                control = (Control)in.readObject();
1009            }
1010        }
1011    
1012    
1013        /**
1014         * @see Externalizable#readExternal(ObjectInput)<p>
1015         *
1016         *@param out The stream in which the ChangeLogEvent will be serialized. 
1017         *
1018         *@throws IOException If the serialization fail
1019         */
1020        public void writeExternal( ObjectOutput out ) throws IOException
1021        {
1022            // Write the changeType
1023            out.writeInt( changeType.getChangeType() );
1024            
1025            // Write the entry
1026            out.writeObject( entry );
1027            
1028            // Write the data
1029            switch ( changeType )
1030            {
1031                case Add :
1032                    // Fallback
1033                case Delete :
1034                    // we don't have anything to write, but the control
1035                    break;
1036    
1037                case ModDn :
1038                    // Fallback
1039                case ModRdn :
1040                    out.writeBoolean( deleteOldRdn );
1041                    
1042                    if ( newRdn != null )
1043                    {
1044                        out.writeBoolean( true );
1045                        out.writeUTF( newRdn );
1046                    }
1047                    else
1048                    {
1049                        out.writeBoolean( false );
1050                    }
1051                    
1052                    if ( newSuperior != null )
1053                    {
1054                        out.writeBoolean( true );
1055                        out.writeUTF( newSuperior );
1056                    }
1057                    else
1058                    {
1059                        out.writeBoolean( false );
1060                    }
1061                    break;
1062                    
1063                case Modify :
1064                    // Read the modification
1065                    out.writeInt( modificationList.size() );
1066                    
1067                    for ( Modification modification:modificationList )
1068                    {
1069                        out.writeInt( modification.getOperation().getValue() );
1070                        out.writeUTF( modification.getAttribute().getId() );
1071                        
1072                        EntryAttribute attribute = modification.getAttribute();
1073                        out.writeObject( attribute );
1074                    }
1075                    
1076                    break;
1077            }
1078            
1079            if ( control != null )
1080            {
1081                // Write the control
1082                out.writeObject( control );
1083                
1084            }
1085            
1086            // and flush the result
1087            out.flush();
1088        }
1089    }