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