View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.api.ldap.model.ldif;
21  
22  
23  import java.io.Externalizable;
24  import java.io.IOException;
25  import java.io.ObjectInput;
26  import java.io.ObjectOutput;
27  import java.util.HashMap;
28  import java.util.LinkedList;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.concurrent.ConcurrentHashMap;
32  
33  import org.apache.directory.api.i18n.I18n;
34  import org.apache.directory.api.ldap.model.entry.Attribute;
35  import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
36  import org.apache.directory.api.ldap.model.entry.DefaultEntry;
37  import org.apache.directory.api.ldap.model.entry.DefaultModification;
38  import org.apache.directory.api.ldap.model.entry.Entry;
39  import org.apache.directory.api.ldap.model.entry.Modification;
40  import org.apache.directory.api.ldap.model.entry.ModificationOperation;
41  import org.apache.directory.api.ldap.model.entry.StringValue;
42  import org.apache.directory.api.ldap.model.entry.Value;
43  import org.apache.directory.api.ldap.model.exception.LdapException;
44  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
45  import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
46  import org.apache.directory.api.ldap.model.message.Control;
47  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
48  import org.apache.directory.api.ldap.model.name.Dn;
49  import org.apache.directory.api.ldap.model.name.Rdn;
50  import org.apache.directory.api.util.Base64;
51  import org.apache.directory.api.util.Strings;
52  
53  
54  /**
55   * A entry to be populated by an ldif parser.
56   * 
57   * We will have different kind of entries : 
58   * <ul>
59   * <li>added entries</li>
60   * <li>deleted entries</li>
61   * <li>modified entries</li>
62   * <li>Rdn modified entries</li>
63   * <li>Dn modified entries</li>
64   * </ul>
65   * 
66   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
67   */
68  public class LdifEntry implements Cloneable, Externalizable
69  {
70      /** Used in toArray() */
71      public static final Modification[] EMPTY_MODS = new Modification[0];
72  
73      /** the change type */
74      private ChangeType changeType;
75  
76      /** the modification item list */
77      private List<Modification> modificationList;
78  
79      /** The map containing all the modifications */
80      private Map<String, Modification> modifications;
81  
82      /** The new superior */
83      private String newSuperior;
84  
85      /** The new rdn */
86      private String newRdn;
87  
88      /** The delete old rdn flag */
89      private boolean deleteOldRdn;
90  
91      /** the entry */
92      private Entry entry;
93  
94      /** the DN */
95      private Dn entryDn;
96  
97      /** The controls */
98      private Map<String, LdifControl> controls;
99  
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 }