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