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