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.BufferedReader;
024import java.io.Closeable;
025import java.io.DataInputStream;
026import java.io.File;
027import java.io.FileInputStream;
028import java.io.FileNotFoundException;
029import java.io.FileReader;
030import java.io.IOException;
031import java.io.InputStream;
032import java.io.InputStreamReader;
033import java.io.Reader;
034import java.io.StringReader;
035import java.io.UnsupportedEncodingException;
036import java.net.MalformedURLException;
037import java.net.URL;
038import java.nio.charset.Charset;
039import java.util.ArrayList;
040import java.util.Iterator;
041import java.util.List;
042import java.util.NoSuchElementException;
043
044import org.apache.directory.api.asn1.util.Oid;
045import org.apache.directory.api.i18n.I18n;
046import org.apache.directory.api.ldap.model.entry.Attribute;
047import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
048import org.apache.directory.api.ldap.model.entry.ModificationOperation;
049import org.apache.directory.api.ldap.model.exception.LdapException;
050import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
051import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
052import org.apache.directory.api.ldap.model.message.Control;
053import org.apache.directory.api.ldap.model.name.Dn;
054import org.apache.directory.api.ldap.model.schema.SchemaManager;
055import org.apache.directory.api.util.Base64;
056import org.apache.directory.api.util.Chars;
057import org.apache.directory.api.util.Strings;
058import org.apache.directory.api.util.exception.NotImplementedException;
059import org.slf4j.Logger;
060import org.slf4j.LoggerFactory;
061
062
063/**
064 * <pre>
065 *  &lt;ldif-file&gt; ::= &quot;version:&quot; &lt;fill&gt; &lt;number&gt; &lt;seps&gt; &lt;dn-spec&gt; &lt;sep&gt;
066 *  &lt;ldif-content-change&gt;
067 *
068 *  &lt;ldif-content-change&gt; ::=
069 *    &lt;number&gt; &lt;oid&gt; &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt;
070 *    &lt;attrval-specs-e&gt; &lt;ldif-attrval-record-e&gt; |
071 *    &lt;alpha&gt; &lt;chars-e&gt; &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt;
072 *    &lt;attrval-specs-e&gt; &lt;ldif-attrval-record-e&gt; |
073 *    &quot;control:&quot; &lt;fill&gt; &lt;number&gt; &lt;oid&gt; &lt;spaces-e&gt;
074 *    &lt;criticality&gt; &lt;value-spec-e&gt; &lt;sep&gt; &lt;controls-e&gt;
075 *        &quot;changetype:&quot; &lt;fill&gt; &lt;changerecord-type&gt; &lt;ldif-change-record-e&gt; |
076 *    &quot;changetype:&quot; &lt;fill&gt; &lt;changerecord-type&gt; &lt;ldif-change-record-e&gt;
077 *
078 *  &lt;ldif-attrval-record-e&gt; ::= &lt;seps&gt; &lt;dn-spec&gt; &lt;sep&gt; &lt;attributeType&gt;
079 *    &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt; &lt;attrval-specs-e&gt;
080 *    &lt;ldif-attrval-record-e&gt; | e
081 *
082 *  &lt;ldif-change-record-e&gt; ::= &lt;seps&gt; &lt;dn-spec&gt; &lt;sep&gt; &lt;controls-e&gt;
083 *    &quot;changetype:&quot; &lt;fill&gt; &lt;changerecord-type&gt; &lt;ldif-change-record-e&gt; | e
084 *
085 *  &lt;dn-spec&gt; ::= &quot;dn:&quot; &lt;fill&gt; &lt;safe-string&gt; | &quot;dn::&quot; &lt;fill&gt; &lt;base64-string&gt;
086 *
087 *  &lt;controls-e&gt; ::= &quot;control:&quot; &lt;fill&gt; &lt;number&gt; &lt;oid&gt; &lt;spaces-e&gt; &lt;criticality&gt;
088 *    &lt;value-spec-e&gt; &lt;sep&gt; &lt;controls-e&gt; | e
089 *
090 *  &lt;criticality&gt; ::= &quot;true&quot; | &quot;false&quot; | e
091 *
092 *  &lt;oid&gt; ::= '.' &lt;number&gt; &lt;oid&gt; | e
093 *
094 *  &lt;attrval-specs-e&gt; ::= &lt;number&gt; &lt;oid&gt; &lt;options-e&gt; &lt;value-spec&gt;
095 *  &lt;sep&gt; &lt;attrval-specs-e&gt; |
096 *    &lt;alpha&gt; &lt;chars-e&gt; &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt; &lt;attrval-specs-e&gt; | e
097 *
098 *  &lt;value-spec-e&gt; ::= &lt;value-spec&gt; | e
099 *
100 *  &lt;value-spec&gt; ::= ':' &lt;fill&gt; &lt;safe-string-e&gt; |
101 *    &quot;::&quot; &lt;fill&gt; &lt;base64-chars&gt; |
102 *    &quot;:&lt;&quot; &lt;fill&gt; &lt;url&gt;
103 *
104 *  &lt;attributeType&gt; ::= &lt;number&gt; &lt;oid&gt; | &lt;alpha&gt; &lt;chars-e&gt;
105 *
106 *  &lt;options-e&gt; ::= ';' &lt;char&gt; &lt;chars-e&gt; &lt;options-e&gt; |e
107 *
108 *  &lt;chars-e&gt; ::= &lt;char&gt; &lt;chars-e&gt; |  e
109 *
110 *  &lt;changerecord-type&gt; ::= &quot;add&quot; &lt;sep&gt; &lt;attributeType&gt;
111 *  &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt; &lt;attrval-specs-e&gt; |
112 *    &quot;delete&quot; &lt;sep&gt; |
113 *    &quot;modify&quot; &lt;sep&gt; &lt;mod-type&gt; &lt;fill&gt; &lt;attributeType&gt;
114 *    &lt;options-e&gt; &lt;sep&gt; &lt;attrval-specs-e&gt; &lt;sep&gt; '-' &lt;sep&gt; &lt;mod-specs-e&gt; |
115 *    &quot;moddn&quot; &lt;sep&gt; &lt;newrdn&gt; &lt;sep&gt; &quot;deleteoldrdn:&quot;
116 *    &lt;fill&gt; &lt;0-1&gt; &lt;sep&gt; &lt;newsuperior-e&gt; &lt;sep&gt; |
117 *    &quot;modrdn&quot; &lt;sep&gt; &lt;newrdn&gt; &lt;sep&gt; &quot;deleteoldrdn:&quot;
118 *    &lt;fill&gt; &lt;0-1&gt; &lt;sep&gt; &lt;newsuperior-e&gt; &lt;sep&gt;
119 *
120 *  &lt;newrdn&gt; ::= ':' &lt;fill&gt; &lt;safe-string&gt; | &quot;::&quot; &lt;fill&gt; &lt;base64-chars&gt;
121 *
122 *  &lt;newsuperior-e&gt; ::= &quot;newsuperior&quot; &lt;newrdn&gt; | e
123 *
124 *  &lt;mod-specs-e&gt; ::= &lt;mod-type&gt; &lt;fill&gt; &lt;attributeType&gt; &lt;options-e&gt;
125 *    &lt;sep&gt; &lt;attrval-specs-e&gt; &lt;sep&gt; '-' &lt;sep&gt; &lt;mod-specs-e&gt; | e
126 *
127 *  &lt;mod-type&gt; ::= &quot;add:&quot; | &quot;delete:&quot; | &quot;replace:&quot;
128 *
129 *  &lt;url&gt; ::= &lt;a Uniform Resource Locator, as defined in [6]&gt;
130 *
131 *
132 *
133 *  LEXICAL
134 *  -------
135 *
136 *  &lt;fill&gt;           ::= ' ' &lt;fill&gt; | e
137 *  &lt;char&gt;           ::= &lt;alpha&gt; | &lt;digit&gt; | '-'
138 *  &lt;number&gt;         ::= &lt;digit&gt; &lt;digits&gt;
139 *  &lt;0-1&gt;            ::= '0' | '1'
140 *  &lt;digits&gt;         ::= &lt;digit&gt; &lt;digits&gt; | e
141 *  &lt;digit&gt;          ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
142 *  &lt;seps&gt;           ::= &lt;sep&gt; &lt;seps-e&gt;
143 *  &lt;seps-e&gt;         ::= &lt;sep&gt; &lt;seps-e&gt; | e
144 *  &lt;sep&gt;            ::= 0x0D 0x0A | 0x0A
145 *  &lt;spaces&gt;         ::= ' ' &lt;spaces-e&gt;
146 *  &lt;spaces-e&gt;       ::= ' ' &lt;spaces-e&gt; | e
147 *  &lt;safe-string-e&gt;  ::= &lt;safe-string&gt; | e
148 *  &lt;safe-string&gt;    ::= &lt;safe-init-char&gt; &lt;safe-chars&gt;
149 *  &lt;safe-init-char&gt; ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x1F] | [0x21-0x39] | 0x3B | [0x3D-0x7F]
150 *  &lt;safe-chars&gt;     ::= &lt;safe-char&gt; &lt;safe-chars&gt; | e
151 *  &lt;safe-char&gt;      ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x7F]
152 *  &lt;base64-string&gt;  ::= &lt;base64-char&gt; &lt;base64-chars&gt;
153 *  &lt;base64-chars&gt;   ::= &lt;base64-char&gt; &lt;base64-chars&gt; | e
154 *  &lt;base64-char&gt;    ::= 0x2B | 0x2F | [0x30-0x39] | 0x3D | [0x41-9x5A] | [0x61-0x7A]
155 *  &lt;alpha&gt;          ::= [0x41-0x5A] | [0x61-0x7A]
156 *
157 *  COMMENTS
158 *  --------
159 *  - The ldap-oid VN is not correct in the RFC-2849. It has been changed from 1*DIGIT 0*1(&quot;.&quot; 1*DIGIT) to
160 *  DIGIT+ (&quot;.&quot; DIGIT+)*
161 *  - The mod-spec lacks a sep between *attrval-spec and &quot;-&quot;.
162 *  - The BASE64-UTF8-STRING should be BASE64-CHAR BASE64-STRING
163 *  - The ValueSpec rule must accept multilines values. In this case, we have a LF followed by a
164 *  single space before the continued value.
165 * </pre>
166 *
167 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
168 */
169public class LdifReader implements Iterable<LdifEntry>, Closeable
170{
171    /** A logger */
172    private static final Logger LOG = LoggerFactory.getLogger( LdifReader.class );
173
174    /** A list of read lines */
175    protected List<String> lines;
176
177    /** The current position */
178    protected int position;
179
180    /** The ldif file version default value */
181    protected static final int DEFAULT_VERSION = 1;
182
183    /** The ldif version */
184    protected int version;
185
186    /** Type of element read : ENTRY */
187    protected static final int LDIF_ENTRY = 0;
188
189    /** Type of element read : CHANGE */
190    protected static final int CHANGE = 1;
191
192    /** Type of element read : UNKNOWN */
193    protected static final int UNKNOWN = 2;
194
195    /** Size limit for file contained values */
196    protected long sizeLimit = SIZE_LIMIT_DEFAULT;
197
198    /** The default size limit : 1Mo */
199    protected static final long SIZE_LIMIT_DEFAULT = 1024000;
200
201    /** State values for the modify operation : MOD_SPEC */
202    protected static final int MOD_SPEC = 0;
203
204    /** State values for the modify operation : ATTRVAL_SPEC */
205    protected static final int ATTRVAL_SPEC = 1;
206
207    /** State values for the modify operation : ATTRVAL_SPEC_OR_SEP */
208    protected static final int ATTRVAL_SPEC_OR_SEP = 2;
209
210    /** Iterator prefetched entry */
211    protected LdifEntry prefetched;
212
213    /** The ldif Reader */
214    protected Reader reader;
215
216    /** A flag set if the ldif contains entries */
217    protected boolean containsEntries;
218
219    /** A flag set if the ldif contains changes */
220    protected boolean containsChanges;
221
222    /** The SchemaManager instance, if any */
223    protected SchemaManager schemaManager;
224
225    /**
226     * An Exception to handle error message, has Iterator.next() can't throw
227     * exceptions
228     */
229    protected Exception error;
230
231    /** total length of an LDIF entry including the comments */
232    protected int entryLen = 0;
233
234    /** the parsed entry's starting position */
235    protected long entryOffset = 0;
236
237    /** the current offset of the reader */
238    protected long offset = 0;
239
240    /** the numer of the current line being parsed by the reader */
241    protected int lineNumber;
242
243    /** flag to turn on/off of the DN validation. By default DNs are validated after parsing */
244    protected boolean validateDn = true;
245
246
247    /**
248     * Constructors
249     */
250    public LdifReader()
251    {
252        lines = new ArrayList<String>();
253        position = 0;
254        version = DEFAULT_VERSION;
255    }
256
257
258    /**
259     * Constructors
260     */
261    public LdifReader( SchemaManager schemaManager )
262    {
263        lines = new ArrayList<String>();
264        position = 0;
265        version = DEFAULT_VERSION;
266        this.schemaManager = schemaManager;
267    }
268
269
270    /**
271     * Store the reader and intialize the LdifReader
272     */
273    private void initReader( BufferedReader reader ) throws LdapException
274    {
275        this.reader = reader;
276        init();
277    }
278
279
280    /**
281     * Initialize the LdifReader
282     * 
283     * @throws LdapException If the initialization failed
284     */
285    public void init() throws LdapException
286    {
287        lines = new ArrayList<String>();
288        position = 0;
289        version = DEFAULT_VERSION;
290        containsChanges = false;
291        containsEntries = false;
292
293        // First get the version - if any -
294        version = parseVersion();
295        prefetched = parseEntry();
296    }
297
298
299    /**
300     * A constructor which takes a file name
301     *
302     * @param ldifFileName A file name containing ldif formated input
303     * @throws LdapLdifException If the file cannot be processed or if the format is incorrect
304     */
305    public LdifReader( String ldifFileName ) throws LdapLdifException
306    {
307        File file = new File( ldifFileName );
308
309        if ( !file.exists() )
310        {
311            String msg = I18n.err( I18n.ERR_12010_CANNOT_FIND_FILE, file.getAbsoluteFile() );
312            LOG.error( msg );
313            throw new LdapLdifException( msg );
314        }
315
316        if ( !file.canRead() )
317        {
318            String msg = I18n.err( I18n.ERR_12011_CANNOT_READ_FILE, file.getName() );
319            LOG.error( msg );
320            throw new LdapLdifException( msg );
321        }
322
323        try
324        {
325            initReader( new BufferedReader( new FileReader( file ) ) );
326        }
327        catch ( FileNotFoundException fnfe )
328        {
329            String msg = I18n.err( I18n.ERR_12010_CANNOT_FIND_FILE, file.getAbsoluteFile() );
330            LOG.error( msg );
331            throw new LdapLdifException( msg, fnfe );
332        }
333        catch ( LdapInvalidDnException lide )
334        {
335            throw new LdapLdifException( lide.getMessage(), lide );
336        }
337        catch ( LdapException le )
338        {
339            throw new LdapLdifException( le.getMessage(), le );
340        }
341    }
342
343
344    /**
345     * A constructor which takes a Reader
346     *
347     * @param in A Reader containing ldif formated input
348     * @throws LdapException If the file cannot be processed or if the format is incorrect
349     */
350    public LdifReader( Reader in ) throws LdapException
351    {
352        initReader( new BufferedReader( in ) );
353    }
354
355
356    /**
357     * A constructor which takes an InputStream
358     *
359     * @param in An InputStream containing ldif formated input
360     * @throws LdapException If the file cannot be processed or if the format is incorrect
361     */
362    public LdifReader( InputStream in ) throws LdapException
363    {
364        initReader( new BufferedReader( new InputStreamReader( in ) ) );
365    }
366
367
368    /**
369     * A constructor which takes a File
370     *
371     * @param file A File containing ldif formated input
372     * @throws LdapLdifException If the file cannot be processed or if the format is incorrect
373     */
374    public LdifReader( File file ) throws LdapLdifException
375    {
376        if ( !file.exists() )
377        {
378            String msg = I18n.err( I18n.ERR_12010_CANNOT_FIND_FILE, file.getAbsoluteFile() );
379            LOG.error( msg );
380            throw new LdapLdifException( msg );
381        }
382
383        if ( !file.canRead() )
384        {
385            String msg = I18n.err( I18n.ERR_12011_CANNOT_READ_FILE, file.getName() );
386            LOG.error( msg );
387            throw new LdapLdifException( msg );
388        }
389
390        try
391        {
392            initReader( new BufferedReader( new FileReader( file ) ) );
393        }
394        catch ( FileNotFoundException fnfe )
395        {
396            String msg = I18n.err( I18n.ERR_12010_CANNOT_FIND_FILE, file.getAbsoluteFile() );
397            LOG.error( msg );
398            throw new LdapLdifException( msg, fnfe );
399        }
400        catch ( LdapInvalidDnException lide )
401        {
402            throw new LdapLdifException( lide.getMessage(), lide );
403        }
404        catch ( LdapException le )
405        {
406            throw new LdapLdifException( le.getMessage(), le );
407        }
408    }
409
410
411    /**
412     * A constructor which takes a File and a SchemaManager
413     *
414     * @param file A File containing ldif formated input
415     * @param schemaManager The SchemaManager instance to use
416     * @throws LdapLdifException If the file cannot be processed or if the format is incorrect
417     */
418    public LdifReader( File file, SchemaManager schemaManager ) throws LdapLdifException
419    {
420        if ( !file.exists() )
421        {
422            String msg = I18n.err( I18n.ERR_12010_CANNOT_FIND_FILE, file.getAbsoluteFile() );
423            LOG.error( msg );
424            throw new LdapLdifException( msg );
425        }
426
427        if ( !file.canRead() )
428        {
429            String msg = I18n.err( I18n.ERR_12011_CANNOT_READ_FILE, file.getName() );
430            LOG.error( msg );
431            throw new LdapLdifException( msg );
432        }
433
434        this.schemaManager = schemaManager;
435
436        try
437        {
438            initReader( new BufferedReader( new FileReader( file ) ) );
439        }
440        catch ( FileNotFoundException fnfe )
441        {
442            String msg = I18n.err( I18n.ERR_12010_CANNOT_FIND_FILE, file.getAbsoluteFile() );
443            LOG.error( msg );
444            throw new LdapLdifException( msg, fnfe );
445        }
446        catch ( LdapInvalidDnException lide )
447        {
448            throw new LdapLdifException( lide.getMessage(), lide );
449        }
450        catch ( LdapException le )
451        {
452            throw new LdapLdifException( le.getMessage(), le );
453        }
454    }
455
456
457    /**
458     * @return The ldif file version
459     */
460    public int getVersion()
461    {
462        return version;
463    }
464
465
466    /**
467     * @return The maximum size of a file which is used into an attribute value.
468     */
469    public long getSizeLimit()
470    {
471        return sizeLimit;
472    }
473
474
475    /**
476     * Set the maximum file size that can be accepted for an attribute value
477     *
478     * @param sizeLimit The size in bytes
479     */
480    public void setSizeLimit( long sizeLimit )
481    {
482        this.sizeLimit = sizeLimit;
483    }
484
485
486    // <fill> ::= ' ' <fill> | e
487    private void parseFill( char[] document )
488    {
489        while ( Chars.isCharASCII( document, position, ' ' ) )
490        {
491            position++;
492        }
493    }
494
495
496    /**
497     * Parse a number following the rules :
498     *
499     * &lt;number&gt; ::= &lt;digit&gt; &lt;digits&gt; &lt;digits&gt; ::= &lt;digit&gt; &lt;digits&gt; | e &lt;digit&gt;
500     * ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
501     *
502     * Check that the number is in the interval
503     *
504     * @param document The document containing the number to parse
505     * @return a String representing the parsed number
506     */
507    private String parseNumber( char[] document )
508    {
509        int initPos = position;
510
511        while ( true )
512        {
513            if ( Chars.isDigit( document, position ) )
514            {
515                position++;
516            }
517            else
518            {
519                break;
520            }
521        }
522
523        if ( position == initPos )
524        {
525            return null;
526        }
527        else
528        {
529            return new String( document, initPos, position - initPos );
530        }
531    }
532
533
534    /**
535     * Parse the changeType
536     *
537     * @param line The line which contains the changeType
538     * @return The operation.
539     */
540    protected ChangeType parseChangeType( String line )
541    {
542        ChangeType operation = ChangeType.Add;
543
544        String modOp = Strings.trim( line.substring( "changetype:".length() + 1 ) );
545
546        if ( "add".equalsIgnoreCase( modOp ) )
547        {
548            operation = ChangeType.Add;
549        }
550        else if ( "delete".equalsIgnoreCase( modOp ) )
551        {
552            operation = ChangeType.Delete;
553        }
554        else if ( "modify".equalsIgnoreCase( modOp ) )
555        {
556            operation = ChangeType.Modify;
557        }
558        else if ( "moddn".equalsIgnoreCase( modOp ) )
559        {
560            operation = ChangeType.ModDn;
561        }
562        else if ( "modrdn".equalsIgnoreCase( modOp ) )
563        {
564            operation = ChangeType.ModRdn;
565        }
566
567        return operation;
568    }
569
570
571    /**
572     * Parse the Dn of an entry
573     *
574     * @param line The line to parse
575     * @return A Dn
576     * @throws LdapLdifException If the Dn is invalid
577     */
578    protected String parseDn( String line ) throws LdapLdifException
579    {
580        String dn;
581
582        String lowerLine = Strings.toLowerCase( line );
583
584        if ( lowerLine.startsWith( "dn:" ) || lowerLine.startsWith( "Dn:" ) )
585        {
586            // Ok, we have a Dn. Is it base 64 encoded ?
587            int length = line.length();
588
589            if ( length == 3 )
590            {
591                // The Dn is empty : it's a rootDSE
592                dn = "";
593            }
594            else if ( line.charAt( 3 ) == ':' )
595            {
596                if ( length > 4 )
597                {
598                    // This is a base 64 encoded Dn.
599                    String trimmedLine = line.substring( 4 ).trim();
600
601                    try
602                    {
603                        dn = new String( Base64.decode( trimmedLine.toCharArray() ), "UTF-8" );
604                    }
605                    catch ( UnsupportedEncodingException uee )
606                    {
607                        // The Dn is not base 64 encoded
608                        LOG.error( I18n.err( I18n.ERR_12014_BASE64_DN_EXPECTED, lineNumber ) );
609                        throw new LdapLdifException( I18n.err( I18n.ERR_12015_INVALID_BASE64_DN ), uee );
610                    }
611                }
612                else
613                {
614                    // The Dn is empty : error
615                    LOG.error( I18n.err( I18n.ERR_12012_EMPTY_DN_NOT_ALLOWED, lineNumber ) );
616                    throw new LdapLdifException( I18n.err( I18n.ERR_12013_NO_DN ) );
617                }
618            }
619            else
620            {
621                dn = line.substring( 3 ).trim();
622            }
623        }
624        else
625        {
626            LOG.error( I18n.err( I18n.ERR_12016_DN_EXPECTED, lineNumber ) );
627            throw new LdapLdifException( I18n.err( I18n.ERR_12013_NO_DN ) );
628        }
629
630        // Check that the Dn is valid. If not, an exception will be thrown
631        if ( validateDn && !Dn.isValid( dn ) )
632        {
633            String message = I18n.err( I18n.ERR_12017_INVALID_DN, dn, lineNumber );
634            LOG.error( message );
635            throw new LdapLdifException( message );
636        }
637
638        return dn;
639    }
640
641
642    /**
643     * Parse the value part.
644     *
645     * @param line The line which contains the value
646     * @param pos The starting position in the line
647     * @return A String or a byte[], depending of the kind of value we get
648     */
649    protected static Object parseSimpleValue( String line, int pos )
650    {
651        if ( line.length() > pos + 1 )
652        {
653            char c = line.charAt( pos + 1 );
654
655            if ( c == ':' )
656            {
657                String value = Strings.trim( line.substring( pos + 2 ) );
658
659                return Base64.decode( value.toCharArray() );
660            }
661            else
662            {
663                return Strings.trim( line.substring( pos + 1 ) );
664            }
665        }
666        else
667        {
668            return null;
669        }
670    }
671
672
673    /**
674     * Parse the value part.
675     *
676     * @param line The line which contains the value
677     * @param pos The starting position in the line
678     * @return A String or a byte[], depending of the kind of value we get
679     * @throws LdapLdifException If something went wrong
680     */
681    protected Object parseValue( String line, int pos ) throws LdapLdifException
682    {
683        if ( line.length() > pos + 1 )
684        {
685            char c = line.charAt( pos + 1 );
686
687            if ( c == ':' )
688            {
689                String value = Strings.trim( line.substring( pos + 2 ) );
690
691                return Base64.decode( value.toCharArray() );
692            }
693            else if ( c == '<' )
694            {
695                String urlName = Strings.trim( line.substring( pos + 2 ) );
696
697                try
698                {
699                    URL url = new URL( urlName );
700
701                    if ( "file".equals( url.getProtocol() ) )
702                    {
703                        String fileName = url.getFile();
704
705                        File file = new File( fileName );
706
707                        if ( !file.exists() )
708                        {
709                            LOG.error( I18n.err( I18n.ERR_12018_FILE_NOT_FOUND, fileName, lineNumber ) );
710                            throw new LdapLdifException( I18n.err( I18n.ERR_12019_BAD_URL_FILE_NOT_FOUND ) );
711                        }
712                        else
713                        {
714                            long length = file.length();
715
716                            if ( length > sizeLimit )
717                            {
718                                String message = I18n.err( I18n.ERR_12020_FILE_TOO_BIG, fileName, lineNumber );
719                                LOG.error( message );
720                                throw new LdapLdifException( message );
721                            }
722                            else
723                            {
724                                byte[] data = new byte[( int ) length];
725                                DataInputStream inf = null;
726
727                                try
728                                {
729                                    inf = new DataInputStream( new FileInputStream( file ) );
730                                    inf.readFully( data );
731
732                                    return data;
733                                }
734                                catch ( FileNotFoundException fnfe )
735                                {
736                                    // We can't reach this point, the file
737                                    // existence has already been
738                                    // checked
739                                    LOG.error( I18n.err( I18n.ERR_12018_FILE_NOT_FOUND, fileName, lineNumber ) );
740                                    throw new LdapLdifException( I18n.err( I18n.ERR_12019_BAD_URL_FILE_NOT_FOUND ),
741                                        fnfe );
742                                }
743                                catch ( IOException ioe )
744                                {
745                                    LOG.error( I18n.err( I18n.ERR_12022_ERROR_READING_FILE, fileName, lineNumber ) );
746                                    throw new LdapLdifException( I18n.err( I18n.ERR_12023_ERROR_READING_BAD_URL ), ioe );
747                                }
748                                finally
749                                {
750                                    try
751                                    {
752                                        if ( inf != null )
753                                        {
754                                            inf.close();
755                                        }
756                                    }
757                                    catch ( IOException ioe )
758                                    {
759                                        LOG.error(
760                                            I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE, ioe.getMessage(), lineNumber ),
761                                            ioe );
762                                        // Just do nothing ...
763                                    }
764                                }
765                            }
766                        }
767                    }
768                    else
769                    {
770                        LOG.error( I18n.err( I18n.ERR_12025_BAD_PROTOCOL ) );
771                        throw new LdapLdifException( I18n.err( I18n.ERR_12026_UNSUPPORTED_PROTOCOL, lineNumber ) );
772                    }
773                }
774                catch ( MalformedURLException mue )
775                {
776                    String message = I18n.err( I18n.ERR_12027_BAD_URL, urlName, lineNumber );
777                    LOG.error( message );
778                    throw new LdapLdifException( message, mue );
779                }
780            }
781            else
782            {
783                String value = Strings.trimLeft( line.substring( pos + 1 ) );
784                int end = value.length();
785
786                for ( int i = value.length() - 1; i > 0; i-- )
787                {
788                    char cc = value.charAt( i );
789
790                    if ( cc == ' ' )
791                    {
792                        if ( value.charAt( i - 1 ) == '\\' )
793                        {
794                            // Escaped space : do nothing
795                            break;
796                        }
797                        else
798                        {
799                            end = i;
800                        }
801                    }
802                    else
803                    {
804                        break;
805                    }
806                }
807
808                String result = null;
809
810                result = value.substring( 0, end );
811
812                return result;
813            }
814        }
815        else
816        {
817            return null;
818        }
819    }
820
821
822    /**
823     * Parse a control. The grammar is :
824     * <pre>
825     * &lt;control&gt; ::= "control:" &lt;fill&gt; &lt;ldap-oid&gt; &lt;critical-e&gt; &lt;value-spec-e&gt; &lt;sep&gt;
826     * &lt;critical-e&gt; ::= &lt;spaces&gt; &lt;boolean&gt; | e
827     * &lt;boolean&gt; ::= "true" | "false"
828     * &lt;value-spec-e&gt; ::= &lt;value-spec&gt; | e
829     * &lt;value-spec&gt; ::= ":" &lt;fill&gt; &lt;SAFE-STRING-e&gt; | "::" &lt;fill&gt; &lt;BASE64-STRING&gt; | ":<" &lt;fill&gt; &lt;url&gt;
830     * </pre>
831     *
832     * It can be read as :
833     * <pre>
834     * "control:" &lt;fill&gt; &lt;ldap-oid&gt; [ " "+ ( "true" |
835     * "false") ] [ ":" &lt;fill&gt; &lt;SAFE-STRING-e&gt; | "::" &lt;fill&gt; &lt;BASE64-STRING&gt; | ":<"
836     * &lt;fill&gt; &lt;url&gt; ]
837     * </pre>
838     *
839     * @param line The line containing the control
840     * @return A control
841     * @exception LdapLdifException If the control has no OID or if the OID is incorrect,
842     * of if the criticality is not set when it's mandatory.
843     */
844    private Control parseControl( String line ) throws LdapLdifException
845    {
846        String lowerLine = Strings.toLowerCase( line ).trim();
847        char[] controlValue = line.trim().toCharArray();
848        int pos = 0;
849        int length = controlValue.length;
850
851        // Get the <ldap-oid>
852        if ( pos > length )
853        {
854            // No OID : error !
855            LOG.error( I18n.err( I18n.ERR_12029_CONTROL_WITHOUT_OID, lineNumber ) );
856            throw new LdapLdifException( I18n.err( I18n.ERR_12029_CONTROL_WITHOUT_OID ) );
857        }
858
859        int initPos = pos;
860
861        while ( Chars.isCharASCII( controlValue, pos, '.' ) || Chars.isDigit( controlValue, pos ) )
862        {
863            pos++;
864        }
865
866        if ( pos == initPos )
867        {
868            // Not a valid OID !
869            LOG.error( I18n.err( I18n.ERR_12029_CONTROL_WITHOUT_OID, lineNumber ) );
870            throw new LdapLdifException( I18n.err( I18n.ERR_12029_CONTROL_WITHOUT_OID ) );
871        }
872
873        // Create and check the OID
874        String oidString = lowerLine.substring( 0, pos );
875
876        if ( !Oid.isOid( oidString ) )
877        {
878            String message = I18n.err( I18n.ERR_12031_INVALID_OID, oidString, lineNumber );
879            LOG.error( message );
880            throw new LdapLdifException( message );
881        }
882
883        LdifControl control = new LdifControl( oidString );
884
885        // Get the criticality, if any
886        // Skip the <fill>
887        while ( Chars.isCharASCII( controlValue, pos, ' ' ) )
888        {
889            pos++;
890        }
891
892        // Check if we have a "true" or a "false"
893        int criticalPos = lowerLine.indexOf( ':' );
894
895        int criticalLength;
896
897        if ( criticalPos == -1 )
898        {
899            criticalLength = length - pos;
900        }
901        else
902        {
903            criticalLength = criticalPos - pos;
904        }
905
906        if ( ( criticalLength == 4 ) && ( "true".equalsIgnoreCase( lowerLine.substring( pos, pos + 4 ) ) ) )
907        {
908            control.setCritical( true );
909        }
910        else if ( ( criticalLength == 5 ) && ( "false".equalsIgnoreCase( lowerLine.substring( pos, pos + 5 ) ) ) )
911        {
912            control.setCritical( false );
913        }
914        else if ( criticalLength != 0 )
915        {
916            // If we have a criticality, it should be either "true" or "false",
917            // nothing else
918            LOG.error( I18n.err( I18n.ERR_12033_INVALID_CRITICALITY, lineNumber ) );
919            throw new LdapLdifException( I18n.err( I18n.ERR_12033_INVALID_CRITICALITY ) );
920        }
921
922        if ( criticalPos > 0 )
923        {
924            // We have a value. It can be a normal value, a base64 encoded value
925            // or a file contained value
926            if ( Chars.isCharASCII( controlValue, criticalPos + 1, ':' ) )
927            {
928                // Base 64 encoded value
929
930                // Skip the <fill>
931                pos = criticalPos + 2;
932
933                while ( Chars.isCharASCII( controlValue, pos, ' ' ) )
934                {
935                    pos++;
936                }
937
938                byte[] value = Base64.decode( line.substring( pos ).toCharArray() );
939                control.setValue( value );
940            }
941            else if ( Chars.isCharASCII( controlValue, criticalPos + 1, '<' ) )
942            {
943                // File contained value
944                throw new NotImplementedException( "See DIRSERVER-1547" );
945            }
946            else
947            {
948                // Skip the <fill>
949                pos = criticalPos + 1;
950
951                while ( Chars.isCharASCII( controlValue, pos, ' ' ) )
952                {
953                    pos++;
954                }
955
956                // Standard value
957                byte[] value = new byte[length - pos];
958
959                for ( int i = 0; i < length - pos; i++ )
960                {
961                    value[i] = ( byte ) controlValue[i + pos];
962                }
963
964                control.setValue( value );
965            }
966        }
967
968        return control;
969    }
970
971
972    /**
973     * Parse an AttributeType/AttributeValue
974     *
975     * @param line The line to parse
976     * @return the parsed Attribute
977     */
978    public static Attribute parseAttributeValue( String line )
979    {
980        int colonIndex = line.indexOf( ':' );
981
982        if ( colonIndex != -1 )
983        {
984            String attributeType = Strings.toLowerCase( line ).substring( 0, colonIndex );
985            Object attributeValue = parseSimpleValue( line, colonIndex );
986
987            // Create an attribute
988            if ( attributeValue instanceof String )
989            {
990                return new DefaultAttribute( attributeType, ( String ) attributeValue );
991            }
992            else
993            {
994                return new DefaultAttribute( attributeType, ( byte[] ) attributeValue );
995            }
996        }
997        else
998        {
999            return null;
1000        }
1001    }
1002
1003
1004    /**
1005     * Parse an AttributeType/AttributeValue
1006     *
1007     * @param entry The entry where to store the value
1008     * @param line The line to parse
1009     * @param lowerLine The same line, lowercased
1010     * @throws LdapException If anything goes wrong
1011     */
1012    public void parseAttributeValue( LdifEntry entry, String line, String lowerLine ) throws LdapException
1013    {
1014        int colonIndex = line.indexOf( ':' );
1015
1016        String attributeType = lowerLine.substring( 0, colonIndex );
1017
1018        // We should *not* have a Dn twice
1019        if ( attributeType.equals( "dn" ) )
1020        {
1021            LOG.error( I18n.err( I18n.ERR_12002_ENTRY_WITH_TWO_DNS, lineNumber ) );
1022            throw new LdapLdifException( I18n.err( I18n.ERR_12003_LDIF_ENTRY_WITH_TWO_DNS ) );
1023        }
1024
1025        Object attributeValue = parseValue( line, colonIndex );
1026
1027        // Update the entry
1028        entry.addAttribute( attributeType, attributeValue );
1029    }
1030
1031
1032    /**
1033     * Parse a ModRDN operation
1034     *
1035     * @param entry The entry to update
1036     * @param iter The lines iterator
1037     * @throws LdapLdifException If anything goes wrong
1038     */
1039    private void parseModRdn( LdifEntry entry, Iterator<String> iter ) throws LdapLdifException
1040    {
1041        // We must have two lines : one starting with "newrdn:" or "newrdn::",
1042        // and the second starting with "deleteoldrdn:"
1043        if ( iter.hasNext() )
1044        {
1045            String line = iter.next();
1046            String lowerLine = Strings.toLowerCase( line );
1047
1048            if ( lowerLine.startsWith( "newrdn::" ) || lowerLine.startsWith( "newrdn:" ) )
1049            {
1050                int colonIndex = line.indexOf( ':' );
1051                Object attributeValue = parseValue( line, colonIndex );
1052
1053                if ( attributeValue instanceof String )
1054                {
1055                    entry.setNewRdn( ( String ) attributeValue );
1056                }
1057                else
1058                {
1059                    entry.setNewRdn( Strings.utf8ToString( ( byte[] ) attributeValue ) );
1060                }
1061            }
1062            else
1063            {
1064                LOG.error( I18n.err( I18n.ERR_12035_BAD_MODRDN_OPERATION, lineNumber ) );
1065                throw new LdapLdifException( I18n.err( I18n.ERR_12035_BAD_MODRDN_OPERATION ) );
1066            }
1067        }
1068        else
1069        {
1070            LOG.error( I18n.err( I18n.ERR_12035_BAD_MODRDN_OPERATION, lineNumber ) );
1071            throw new LdapLdifException( I18n.err( I18n.ERR_12035_BAD_MODRDN_OPERATION ) );
1072        }
1073
1074        if ( iter.hasNext() )
1075        {
1076            String line = iter.next();
1077            String lowerLine = Strings.toLowerCase( line );
1078
1079            if ( lowerLine.startsWith( "deleteoldrdn:" ) )
1080            {
1081                int colonIndex = line.indexOf( ':' );
1082                Object attributeValue = parseValue( line, colonIndex );
1083                entry.setDeleteOldRdn( "1".equals( attributeValue ) );
1084            }
1085            else
1086            {
1087                LOG.error( I18n.err( I18n.ERR_12038_NO_DELETEOLDRDN, lineNumber ) );
1088                throw new LdapLdifException( I18n.err( I18n.ERR_12038_NO_DELETEOLDRDN ) );
1089            }
1090        }
1091        else
1092        {
1093            LOG.error( I18n.err( I18n.ERR_12038_NO_DELETEOLDRDN, lineNumber ) );
1094            throw new LdapLdifException( I18n.err( I18n.ERR_12038_NO_DELETEOLDRDN ) );
1095        }
1096    }
1097
1098
1099    /**
1100     * Parse a modify change type.
1101     *
1102     * The grammar is :
1103     * <pre>
1104     * &lt;changerecord&gt; ::= "changetype:" FILL "modify" SEP &lt;mod-spec&gt; &lt;mod-specs-e&gt;
1105     * &lt;mod-spec&gt; ::= "add:" &lt;mod-val&gt; | "delete:" &lt;mod-val-del&gt; | "replace:" &lt;mod-val&gt;
1106     * &lt;mod-specs-e&gt; ::= &lt;mod-spec&gt;
1107     * &lt;mod-specs-e&gt; | e
1108     * &lt;mod-val&gt; ::= FILL ATTRIBUTE-DESCRIPTION SEP ATTRVAL-SPEC &lt;attrval-specs-e&gt; "-" SEP
1109     * &lt;mod-val-del&gt; ::= FILL ATTRIBUTE-DESCRIPTION SEP &lt;attrval-specs-e&gt; "-" SEP
1110     * &lt;attrval-specs-e&gt; ::= ATTRVAL-SPEC &lt;attrval-specs&gt; | e
1111     * </pre>
1112     *
1113     * @param entry The entry to feed
1114     * @param iter The lines
1115     * @exception LdapLdifException If the modify operation is invalid
1116     */
1117    private void parseModify( LdifEntry entry, Iterator<String> iter ) throws LdapLdifException
1118    {
1119        int state = MOD_SPEC;
1120        String modified = null;
1121        ModificationOperation modificationType = ModificationOperation.ADD_ATTRIBUTE;
1122        Attribute attribute = null;
1123
1124        // The following flag is used to deal with empty modifications
1125        boolean isEmptyValue = true;
1126
1127        while ( iter.hasNext() )
1128        {
1129            String line = iter.next();
1130            String lowerLine = Strings.toLowerCase( line );
1131
1132            if ( lowerLine.startsWith( "-" ) )
1133            {
1134                if ( ( state != ATTRVAL_SPEC_OR_SEP ) && ( state != ATTRVAL_SPEC ) )
1135                {
1136                    LOG.error( I18n.err( I18n.ERR_12040_BAD_MODIFY_SEPARATOR, lineNumber ) );
1137                    throw new LdapLdifException( I18n.err( I18n.ERR_12040_BAD_MODIFY_SEPARATOR ) );
1138                }
1139                else
1140                {
1141                    if ( isEmptyValue )
1142                    {
1143                        // Update the entry
1144                        entry.addModification( modificationType, modified, null );
1145                    }
1146                    else
1147                    {
1148                        // Update the entry with the attribute
1149                        entry.addModification( modificationType, attribute );
1150                    }
1151
1152                    state = MOD_SPEC;
1153                    isEmptyValue = true;
1154                }
1155            }
1156            else if ( lowerLine.startsWith( "add:" ) )
1157            {
1158                if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) )
1159                {
1160                    LOG.error( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2, lineNumber ) );
1161                    throw new LdapLdifException( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2 ) );
1162                }
1163
1164                modified = Strings.trim( line.substring( "add:".length() ) );
1165                modificationType = ModificationOperation.ADD_ATTRIBUTE;
1166                attribute = new DefaultAttribute( modified );
1167
1168                state = ATTRVAL_SPEC;
1169            }
1170            else if ( lowerLine.startsWith( "delete:" ) )
1171            {
1172                if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) )
1173                {
1174                    LOG.error( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2, lineNumber ) );
1175                    throw new LdapLdifException( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2 ) );
1176                }
1177
1178                modified = Strings.trim( line.substring( "delete:".length() ) );
1179                modificationType = ModificationOperation.REMOVE_ATTRIBUTE;
1180                attribute = new DefaultAttribute( modified );
1181                isEmptyValue = false;
1182
1183                state = ATTRVAL_SPEC_OR_SEP;
1184            }
1185            else if ( lowerLine.startsWith( "replace:" ) )
1186            {
1187                if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) )
1188                {
1189                    LOG.error( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2, lineNumber ) );
1190                    throw new LdapLdifException( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2 ) );
1191                }
1192
1193                modified = Strings.trim( line.substring( "replace:".length() ) );
1194                modificationType = ModificationOperation.REPLACE_ATTRIBUTE;
1195                attribute = new DefaultAttribute( modified );
1196
1197                state = ATTRVAL_SPEC_OR_SEP;
1198            }
1199            else
1200            {
1201                if ( ( state != ATTRVAL_SPEC ) && ( state != ATTRVAL_SPEC_OR_SEP ) )
1202                {
1203                    LOG.error( I18n.err( I18n.ERR_12040_BAD_MODIFY_SEPARATOR, lineNumber ) );
1204                    throw new LdapLdifException( I18n.err( I18n.ERR_12040_BAD_MODIFY_SEPARATOR ) );
1205                }
1206
1207                // A standard AttributeType/AttributeValue pair
1208                int colonIndex = line.indexOf( ':' );
1209
1210                String attributeType = line.substring( 0, colonIndex );
1211
1212                if ( !attributeType.equalsIgnoreCase( modified ) )
1213                {
1214                    LOG.error( I18n.err( I18n.ERR_12044, lineNumber ) );
1215                    throw new LdapLdifException( I18n.err( I18n.ERR_12045 ) );
1216                }
1217
1218                // We should *not* have a Dn twice
1219                if ( attributeType.equalsIgnoreCase( "dn" ) )
1220                {
1221                    LOG.error( I18n.err( I18n.ERR_12002_ENTRY_WITH_TWO_DNS, lineNumber ) );
1222                    throw new LdapLdifException( I18n.err( I18n.ERR_12003_LDIF_ENTRY_WITH_TWO_DNS ) );
1223                }
1224
1225                Object attributeValue = parseValue( line, colonIndex );
1226
1227                try
1228                {
1229                    if ( attributeValue instanceof String )
1230                    {
1231                        attribute.add( ( String ) attributeValue );
1232                    }
1233                    else
1234                    {
1235                        attribute.add( ( byte[] ) attributeValue );
1236                    }
1237                }
1238                catch ( LdapInvalidAttributeValueException liave )
1239                {
1240                    throw new LdapLdifException( liave.getMessage(), liave );
1241                }
1242
1243                isEmptyValue = false;
1244
1245                state = ATTRVAL_SPEC_OR_SEP;
1246            }
1247        }
1248
1249        if ( state != MOD_SPEC )
1250        {
1251            LOG.error( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2, lineNumber ) );
1252            throw new LdapLdifException( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2 ) );
1253        }
1254    }
1255
1256
1257    /**
1258     * Parse a change operation. We have to handle different cases depending on
1259     * the operation.
1260     * <ul>
1261     * <li>1) Delete : there should *not* be any line after the "changetype: delete" </li>
1262     * <li>2) Add : we must have a list of AttributeType : AttributeValue elements </li>
1263     * <li>3) ModDN : we must have two following lines: a "newrdn:" and a "deleteoldrdn:" </li>
1264     * <li>4) ModRDN : the very same, but a "newsuperior:" line is expected </li>
1265     * <li>5) Modify</li>
1266     * </ul>
1267     *
1268     * The grammar is :
1269     * <pre>
1270     * &lt;changerecord&gt; ::= "changetype:" FILL "add" SEP &lt;attrval-spec&gt; &lt;attrval-specs-e&gt; |
1271     *     "changetype:" FILL "delete" |
1272     *     "changetype:" FILL "modrdn" SEP &lt;newrdn&gt; SEP &lt;deleteoldrdn&gt; SEP |
1273     *     // To be checked
1274     *     "changetype:" FILL "moddn" SEP &lt;newrdn&gt; SEP &lt;deleteoldrdn&gt; SEP &lt;newsuperior&gt; SEP |
1275     *     "changetype:" FILL "modify" SEP &lt;mod-spec&gt; &lt;mod-specs-e&gt;
1276     * &lt;newrdn&gt; ::= "newrdn:" FILL Rdn | "newrdn::" FILL BASE64-Rdn
1277     * &lt;deleteoldrdn&gt; ::= "deleteoldrdn:" FILL "0" | "deleteoldrdn:" FILL "1"
1278     * &lt;newsuperior&gt; ::= "newsuperior:" FILL Dn | "newsuperior::" FILL BASE64-Dn
1279     * &lt;mod-specs-e&gt; ::= &lt;mod-spec&gt; &lt;mod-specs-e&gt; | e
1280     * &lt;mod-spec&gt; ::= "add:" &lt;mod-val&gt; | "delete:" &lt;mod-val&gt; | "replace:" &lt;mod-val&gt;
1281     * &lt;mod-val&gt; ::= FILL ATTRIBUTE-DESCRIPTION SEP ATTRVAL-SPEC &lt;attrval-specs-e&gt; "-" SEP
1282     * &lt;attrval-specs-e&gt; ::= ATTRVAL-SPEC &lt;attrval-specs&gt; | e
1283     * </pre>
1284     *
1285     * @param entry The entry to feed
1286     * @param iter The lines iterator
1287     * @param operation The change operation (add, modify, delete, moddn or modrdn)
1288     * @exception LdapException If the change operation is invalid
1289     */
1290    private void parseChange( LdifEntry entry, Iterator<String> iter, ChangeType operation ) throws LdapException
1291    {
1292        // The changetype and operation has already been parsed.
1293        entry.setChangeType( operation );
1294
1295        switch ( operation )
1296        {
1297            case Delete:
1298                // The change type will tell that it's a delete operation,
1299                // the dn is used as a key.
1300                return;
1301
1302            case Add:
1303                // We will iterate through all attribute/value pairs
1304                while ( iter.hasNext() )
1305                {
1306                    String line = iter.next();
1307                    String lowerLine = Strings.toLowerCase( line );
1308                    parseAttributeValue( entry, line, lowerLine );
1309                }
1310
1311                return;
1312
1313            case Modify:
1314                parseModify( entry, iter );
1315                return;
1316
1317            case ModDn:// They are supposed to have the same syntax ???
1318            case ModRdn:
1319                // First, parse the modrdn part
1320                parseModRdn( entry, iter );
1321
1322                // The next line should be the new superior, if we have one
1323                if ( iter.hasNext() )
1324                {
1325                    String line = iter.next();
1326                    String lowerLine = Strings.toLowerCase( line );
1327
1328                    if ( lowerLine.startsWith( "newsuperior:" ) )
1329                    {
1330                        int colonIndex = line.indexOf( ':' );
1331                        Object attributeValue = parseValue( line, colonIndex );
1332
1333                        if ( attributeValue instanceof String )
1334                        {
1335                            entry.setNewSuperior( ( String ) attributeValue );
1336                        }
1337                        else
1338                        {
1339                            entry.setNewSuperior( Strings.utf8ToString( ( byte[] ) attributeValue ) );
1340                        }
1341                    }
1342                    else
1343                    {
1344                        if ( operation == ChangeType.ModDn )
1345                        {
1346                            LOG.error( I18n.err( I18n.ERR_12046, lineNumber ) );
1347                            throw new LdapLdifException( I18n.err( I18n.ERR_12047 ) );
1348                        }
1349                    }
1350                }
1351
1352                return;
1353
1354            default:
1355                // This is an error
1356                LOG.error( I18n.err( I18n.ERR_12048, lineNumber ) );
1357                throw new LdapLdifException( I18n.err( I18n.ERR_12049 ) );
1358        }
1359    }
1360
1361
1362    /**
1363     * Parse a ldif file. The following rules are processed :
1364     * <pre>
1365     * &lt;ldif-file&gt; ::= &lt;ldif-attrval-record&gt; &lt;ldif-attrval-records&gt; |
1366     *     &lt;ldif-change-record&gt; &lt;ldif-change-records&gt;
1367     * &lt;ldif-attrval-record&gt; ::= &lt;dn-spec&gt; &lt;sep&gt; &lt;attrval-spec&gt; &lt;attrval-specs&gt;
1368     * &lt;ldif-change-record&gt; ::= &lt;dn-spec&gt; &lt;sep&gt; &lt;controls-e&gt; &lt;changerecord&gt;
1369     * &lt;dn-spec&gt; ::= "dn:" &lt;fill&gt; &lt;distinguishedName&gt; | "dn::" &lt;fill&gt; &lt;base64-distinguishedName&gt;
1370     * &lt;changerecord&gt; ::= "changetype:" &lt;fill&gt; &lt;change-op&gt;
1371     * </pre>
1372     *
1373     * @return the parsed ldifEntry
1374     * @exception LdapException If the ldif file does not contain a valid entry
1375     */
1376    protected LdifEntry parseEntry() throws LdapException
1377    {
1378        if ( ( lines == null ) || ( lines.size() == 0 ) )
1379        {
1380            LOG.debug( "The entry is empty : end of ldif file" );
1381            return null;
1382        }
1383
1384        // The entry must start with a dn: or a dn::
1385        String line = lines.get( 0 );
1386
1387        lineNumber -= ( lines.size() - 1 );
1388
1389        String name = parseDn( line );
1390
1391        Dn dn = new Dn( schemaManager, name );
1392
1393        // Ok, we have found a Dn
1394        LdifEntry entry = createLdifEntry( schemaManager );
1395        entry.setLengthBeforeParsing( entryLen );
1396        entry.setOffset( entryOffset );
1397
1398        entry.setDn( dn );
1399
1400        // We remove this dn from the lines
1401        lines.remove( 0 );
1402
1403        // Now, let's iterate through the other lines
1404        Iterator<String> iter = lines.iterator();
1405
1406        // This flag is used to distinguish between an entry and a change
1407        int type = LDIF_ENTRY;
1408
1409        // The following boolean is used to check that a control is *not*
1410        // found elswhere than just after the dn
1411        boolean controlSeen = false;
1412
1413        // We use this boolean to check that we do not have AttributeValues
1414        // after a change operation
1415        boolean changeTypeSeen = false;
1416
1417        ChangeType operation = ChangeType.Add;
1418        String lowerLine;
1419        Control control;
1420
1421        while ( iter.hasNext() )
1422        {
1423            lineNumber++;
1424
1425            // Each line could start either with an OID, an attribute type, with
1426            // "control:" or with "changetype:"
1427            line = iter.next();
1428            lowerLine = Strings.toLowerCase( line );
1429
1430            // We have three cases :
1431            // 1) The first line after the Dn is a "control:"
1432            // 2) The first line after the Dn is a "changeType:"
1433            // 3) The first line after the Dn is anything else
1434            if ( lowerLine.startsWith( "control:" ) )
1435            {
1436                if ( containsEntries )
1437                {
1438                    LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED, lineNumber ) );
1439                    throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) );
1440                }
1441
1442                containsChanges = true;
1443
1444                if ( controlSeen )
1445                {
1446                    LOG.error( I18n.err( I18n.ERR_12050, lineNumber ) );
1447                    throw new LdapLdifException( I18n.err( I18n.ERR_12051 ) );
1448                }
1449
1450                // Parse the control
1451                control = parseControl( line.substring( "control:".length() ) );
1452                entry.addControl( control );
1453            }
1454            else if ( lowerLine.startsWith( "changetype:" ) )
1455            {
1456                if ( containsEntries )
1457                {
1458                    LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED, lineNumber ) );
1459                    throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) );
1460                }
1461
1462                containsChanges = true;
1463
1464                if ( changeTypeSeen )
1465                {
1466                    LOG.error( I18n.err( I18n.ERR_12052, lineNumber ) );
1467                    throw new LdapLdifException( I18n.err( I18n.ERR_12053 ) );
1468                }
1469
1470                // A change request
1471                type = CHANGE;
1472                controlSeen = true;
1473
1474                operation = parseChangeType( line );
1475
1476                // Parse the change operation in a separate function
1477                parseChange( entry, iter, operation );
1478                changeTypeSeen = true;
1479            }
1480            else if ( line.indexOf( ':' ) > 0 )
1481            {
1482                if ( containsChanges )
1483                {
1484                    LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED, lineNumber ) );
1485                    throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) );
1486                }
1487
1488                containsEntries = true;
1489
1490                if ( controlSeen || changeTypeSeen )
1491                {
1492                    LOG.error( I18n.err( I18n.ERR_12054, lineNumber ) );
1493                    throw new LdapLdifException( I18n.err( I18n.ERR_12055 ) );
1494                }
1495
1496                parseAttributeValue( entry, line, lowerLine );
1497                type = LDIF_ENTRY;
1498            }
1499            else
1500            {
1501                // Invalid attribute Value
1502                LOG.error( I18n.err( I18n.ERR_12056, lineNumber ) );
1503                throw new LdapLdifException( I18n.err( I18n.ERR_12057_BAD_ATTRIBUTE ) );
1504            }
1505        }
1506
1507        if ( type == LDIF_ENTRY )
1508        {
1509            LOG.debug( "Read an entry : {}", entry );
1510        }
1511        else if ( type == CHANGE )
1512        {
1513            entry.setChangeType( operation );
1514            LOG.debug( "Read a modification : {}", entry );
1515        }
1516        else
1517        {
1518            LOG.error( I18n.err( I18n.ERR_12058_UNKNOWN_ENTRY_TYPE, lineNumber ) );
1519            throw new LdapLdifException( I18n.err( I18n.ERR_12059_UNKNOWN_ENTRY ) );
1520        }
1521
1522        return entry;
1523    }
1524
1525
1526    /**
1527     * Parse the version from the ldif input.
1528     *
1529     * @return A number representing the version (default to 1)
1530     * @throws LdapLdifException If the version is incorrect or if the input is incorrect
1531     */
1532    protected int parseVersion() throws LdapLdifException
1533    {
1534        int ver = DEFAULT_VERSION;
1535
1536        // First, read a list of lines
1537        readLines();
1538
1539        if ( lines.size() == 0 )
1540        {
1541            LOG.warn( "The ldif file is empty" );
1542            return ver;
1543        }
1544
1545        // get the first line
1546        String line = lines.get( 0 );
1547
1548        // <ldif-file> ::= "version:" <fill> <number>
1549        char[] document = line.toCharArray();
1550        String versionNumber;
1551
1552        if ( line.startsWith( "version:" ) )
1553        {
1554            position += "version:".length();
1555            parseFill( document );
1556
1557            // Version number. Must be '1' in this version
1558            versionNumber = parseNumber( document );
1559
1560            // We should not have any other chars after the number
1561            if ( position != document.length )
1562            {
1563                LOG.error( I18n.err( I18n.ERR_12060_VERSION_NOT_A_NUMBER, lineNumber ) );
1564                throw new LdapLdifException( I18n.err( I18n.ERR_12061_LDIF_PARSING_ERROR ) );
1565            }
1566
1567            try
1568            {
1569                ver = Integer.parseInt( versionNumber );
1570            }
1571            catch ( NumberFormatException nfe )
1572            {
1573                LOG.error( I18n.err( I18n.ERR_12060_VERSION_NOT_A_NUMBER, lineNumber ) );
1574                throw new LdapLdifException( I18n.err( I18n.ERR_12061_LDIF_PARSING_ERROR ), nfe );
1575            }
1576
1577            LOG.debug( "Ldif version : {}", versionNumber );
1578
1579            // We have found the version, just discard the line from the list
1580            lines.remove( 0 );
1581
1582            // and read the next lines if the current buffer is empty
1583            if ( lines.size() == 0 )
1584            {
1585                // include the version line as part of the first entry
1586                int tmpEntryLen = entryLen;
1587
1588                readLines();
1589
1590                entryLen += tmpEntryLen;
1591            }
1592        }
1593        else
1594        {
1595            LOG.info( "No version information : assuming version: 1" );
1596        }
1597
1598        return ver;
1599    }
1600
1601
1602    /**
1603     * gets a line from the underlying data store
1604     *
1605     * @return a line of characters or null if EOF reached
1606     * @throws IOException on read failure
1607     */
1608    protected String getLine() throws IOException
1609    {
1610        return ( ( BufferedReader ) reader ).readLine();
1611    }
1612
1613
1614    /**
1615     * Reads an entry in a ldif buffer, and returns the resulting lines, without
1616     * comments, and unfolded.
1617     *
1618     * The lines represent *one* entry.
1619     *
1620     * @throws LdapLdifException If something went wrong
1621     */
1622    protected void readLines() throws LdapLdifException
1623    {
1624        String line;
1625        boolean insideComment = true;
1626        boolean isFirstLine = true;
1627
1628        lines.clear();
1629        entryLen = 0;
1630        entryOffset = offset;
1631
1632        StringBuffer sb = new StringBuffer();
1633
1634        try
1635        {
1636            while ( ( line = getLine() ) != null )
1637            {
1638                lineNumber++;
1639
1640                if ( line.length() == 0 )
1641                {
1642                    if ( isFirstLine )
1643                    {
1644                        continue;
1645                    }
1646                    else
1647                    {
1648                        // The line is empty, we have read an entry
1649                        insideComment = false;
1650                        offset++;
1651                        break;
1652                    }
1653                }
1654
1655                // We will read the first line which is not a comment
1656                switch ( line.charAt( 0 ) )
1657                {
1658                    case '#':
1659                        insideComment = true;
1660                        break;
1661
1662                    case ' ':
1663                        isFirstLine = false;
1664
1665                        if ( insideComment )
1666                        {
1667                            continue;
1668                        }
1669                        else if ( sb.length() == 0 )
1670                        {
1671                            LOG.error( I18n.err( I18n.ERR_12062_EMPTY_CONTINUATION_LINE, lineNumber ) );
1672                            throw new LdapLdifException( I18n.err( I18n.ERR_12061_LDIF_PARSING_ERROR ) );
1673                        }
1674                        else
1675                        {
1676                            sb.append( line.substring( 1 ) );
1677                        }
1678
1679                        insideComment = false;
1680                        break;
1681
1682                    default:
1683                        isFirstLine = false;
1684
1685                        // We have found a new entry
1686                        // First, stores the previous one if any.
1687                        if ( sb.length() != 0 )
1688                        {
1689                            lines.add( sb.toString() );
1690                        }
1691
1692                        sb = new StringBuffer( line );
1693                        insideComment = false;
1694                        break;
1695                }
1696
1697                byte[] data = line.getBytes();
1698                // FIXME might fail on windows in the new line issue, yet to check
1699                offset += ( data.length + 1 );
1700                entryLen += ( data.length + 1 );
1701            }
1702        }
1703        catch ( IOException ioe )
1704        {
1705            throw new LdapLdifException( I18n.err( I18n.ERR_12063_ERROR_WHILE_READING_LDIF_LINE ), ioe );
1706        }
1707
1708        // Stores the current line if necessary.
1709        if ( sb.length() != 0 )
1710        {
1711            lines.add( sb.toString() );
1712        }
1713    }
1714
1715
1716    /**
1717     * Parse a ldif file (using the default encoding).
1718     *
1719     * @param fileName The ldif file
1720     * @return A list of entries
1721     * @throws LdapLdifException If the parsing fails
1722     */
1723    public List<LdifEntry> parseLdifFile( String fileName ) throws LdapLdifException
1724    {
1725        return parseLdifFile( fileName, Charset.forName( Strings.getDefaultCharsetName() ).toString() );
1726    }
1727
1728
1729    /**
1730     * Parse a ldif file, decoding it using the given charset encoding
1731     *
1732     * @param fileName The ldif file
1733     * @param encoding The charset encoding to use
1734     * @return A list of entries
1735     * @throws LdapLdifException If the parsing fails
1736     */
1737    public List<LdifEntry> parseLdifFile( String fileName, String encoding ) throws LdapLdifException
1738    {
1739        if ( Strings.isEmpty( fileName ) )
1740        {
1741            LOG.error( I18n.err( I18n.ERR_12064_EMPTY_FILE_NAME ) );
1742            throw new LdapLdifException( I18n.err( I18n.ERR_12064_EMPTY_FILE_NAME ) );
1743        }
1744
1745        File file = new File( fileName );
1746
1747        if ( !file.exists() )
1748        {
1749            LOG.error( I18n.err( I18n.ERR_12066, fileName ) );
1750            throw new LdapLdifException( I18n.err( I18n.ERR_12067, fileName ) );
1751        }
1752
1753        BufferedReader reader = null;
1754
1755        // Open the file and then get a channel from the stream
1756        try
1757        {
1758            reader = new BufferedReader(
1759                new InputStreamReader( new FileInputStream( file ), Charset.forName( encoding ) ) );
1760
1761            return parseLdif( reader );
1762        }
1763        catch ( FileNotFoundException fnfe )
1764        {
1765            LOG.error( I18n.err( I18n.ERR_12068, fileName ) );
1766            throw new LdapLdifException( I18n.err( I18n.ERR_12067, fileName ), fnfe );
1767        }
1768        catch ( LdapException le )
1769        {
1770            throw new LdapLdifException( le.getMessage(), le );
1771        }
1772        finally
1773        {
1774            // close the reader
1775            try
1776            {
1777                if ( reader != null )
1778                {
1779                    reader.close();
1780                }
1781            }
1782            catch ( IOException ioe )
1783            {
1784                // Nothing to do
1785            }
1786        }
1787    }
1788
1789
1790    /**
1791     * A method which parses a ldif string and returns a list of entries.
1792     *
1793     * @param ldif The ldif string
1794     * @return A list of entries, or an empty List
1795     * @throws LdapLdifException If something went wrong
1796     */
1797    public List<LdifEntry> parseLdif( String ldif ) throws LdapLdifException
1798    {
1799        LOG.debug( "Starts parsing ldif buffer" );
1800
1801        if ( Strings.isEmpty( ldif ) )
1802        {
1803            return new ArrayList<LdifEntry>();
1804        }
1805
1806        BufferedReader reader = new BufferedReader( new StringReader( ldif ) );
1807
1808        try
1809        {
1810            List<LdifEntry> entries = parseLdif( reader );
1811
1812            if ( LOG.isDebugEnabled() )
1813            {
1814                LOG.debug( "Parsed {} entries.", ( entries == null ? Integer.valueOf( 0 ) : Integer.valueOf( entries
1815                    .size() ) ) );
1816            }
1817
1818            return entries;
1819        }
1820        catch ( LdapLdifException ne )
1821        {
1822            LOG.error( I18n.err( I18n.ERR_12069, ne.getLocalizedMessage() ) );
1823            throw new LdapLdifException( I18n.err( I18n.ERR_12070 ), ne );
1824        }
1825        catch ( LdapException le )
1826        {
1827            throw new LdapLdifException( le.getMessage(), le );
1828        }
1829        finally
1830        {
1831            // Close the reader
1832            try
1833            {
1834                reader.close();
1835            }
1836            catch ( IOException ioe )
1837            {
1838                throw new LdapLdifException( I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE ), ioe );
1839            }
1840
1841        }
1842    }
1843
1844
1845    // ------------------------------------------------------------------------
1846    // Iterator Methods
1847    // ------------------------------------------------------------------------
1848    /**
1849     * Gets the next LDIF on the channel.
1850     *
1851     * @return the next LDIF as a String.
1852     */
1853    private LdifEntry nextInternal()
1854    {
1855        try
1856        {
1857            LOG.debug( "next(): -- called" );
1858
1859            LdifEntry entry = prefetched;
1860            readLines();
1861
1862            try
1863            {
1864                prefetched = parseEntry();
1865            }
1866            catch ( LdapLdifException ne )
1867            {
1868                error = ne;
1869                throw new NoSuchElementException( ne.getMessage() );
1870            }
1871            catch ( LdapException le )
1872            {
1873                throw new NoSuchElementException( le.getMessage() );
1874            }
1875
1876            LOG.debug( "next(): -- returning ldif {}\n", entry );
1877
1878            return entry;
1879        }
1880        catch ( LdapLdifException ne )
1881        {
1882            LOG.error( I18n.err( I18n.ERR_12071 ) );
1883            error = ne;
1884            return null;
1885        }
1886    }
1887
1888
1889    /**
1890     * Gets the next LDIF on the channel.
1891     *
1892     * @return the next LDIF as a String.
1893     */
1894    public LdifEntry next()
1895    {
1896        return nextInternal();
1897    }
1898
1899
1900    /**
1901     * Gets the current entry, but don't move forward.
1902     *
1903     * @return the pre-fetched entry 
1904     */
1905    public LdifEntry fetch()
1906    {
1907        return prefetched;
1908    }
1909
1910
1911    /**
1912     * Tests to see if another LDIF is on the input channel.
1913     *
1914     * @return true if another LDIF is available false otherwise.
1915     */
1916    private boolean hasNextInternal()
1917    {
1918        return null != prefetched;
1919    }
1920
1921
1922    /**
1923     * Tests to see if another LDIF is on the input channel.
1924     *
1925     * @return true if another LDIF is available false otherwise.
1926     */
1927    public boolean hasNext()
1928    {
1929        if ( prefetched != null )
1930        {
1931            LOG.debug( "hasNext(): -- returning true" );
1932        }
1933        else
1934        {
1935            LOG.debug( "hasNext(): -- returning false" );
1936        }
1937
1938        return hasNextInternal();
1939    }
1940
1941
1942    /**
1943     * Always throws UnsupportedOperationException!
1944     *
1945     * @see java.util.Iterator#remove()
1946     */
1947    private void removeInternal()
1948    {
1949        throw new UnsupportedOperationException();
1950    }
1951
1952
1953    /**
1954     * Always throws UnsupportedOperationException!
1955     *
1956     * @see java.util.Iterator#remove()
1957     */
1958    public void remove()
1959    {
1960        removeInternal();
1961    }
1962
1963
1964    /**
1965     * @return An iterator on the file
1966     */
1967    public Iterator<LdifEntry> iterator()
1968    {
1969        return new Iterator<LdifEntry>()
1970        {
1971            public boolean hasNext()
1972            {
1973                return hasNextInternal();
1974            }
1975
1976
1977            public LdifEntry next()
1978            {
1979                return nextInternal();
1980            }
1981
1982
1983            public void remove()
1984            {
1985                throw new UnsupportedOperationException();
1986            }
1987        };
1988    }
1989
1990
1991    /**
1992     * @return True if an error occurred during parsing
1993     */
1994    public boolean hasError()
1995    {
1996        return error != null;
1997    }
1998
1999
2000    /**
2001     * @return The exception that occurs during an entry parsing
2002     */
2003    public Exception getError()
2004    {
2005        return error;
2006    }
2007
2008
2009    /**
2010     * The main entry point of the LdifParser. It reads a buffer and returns a
2011     * List of entries.
2012     *
2013     * @param reader The buffer being processed
2014     * @return A list of entries
2015     * @throws LdapException If something went wrong
2016     */
2017    public List<LdifEntry> parseLdif( BufferedReader reader ) throws LdapException
2018    {
2019        // Create a list that will contain the read entries
2020        List<LdifEntry> entries = new ArrayList<LdifEntry>();
2021
2022        this.reader = reader;
2023
2024        // First get the version - if any -
2025        version = parseVersion();
2026        prefetched = parseEntry();
2027
2028        // When done, get the entries one by one.
2029        try
2030        {
2031            for ( LdifEntry entry : this )
2032            {
2033                if ( entry != null )
2034                {
2035                    entries.add( entry );
2036                }
2037            }
2038        }
2039        catch ( NoSuchElementException nsee )
2040        {
2041            throw new LdapLdifException( I18n.err( I18n.ERR_12072, error.getLocalizedMessage() ), nsee );
2042        }
2043
2044        return entries;
2045    }
2046
2047
2048    /**
2049     * @return True if the ldif file contains entries, fals if it contains changes
2050     */
2051    public boolean containsEntries()
2052    {
2053        return containsEntries;
2054    }
2055
2056
2057    /**
2058     * @return the current line that is being processed by the reader
2059     */
2060    public int getLineNumber()
2061    {
2062        return lineNumber;
2063    }
2064
2065
2066    /**
2067     * creates a non-schemaaware LdifEntry
2068     * @return an LdifEntry that is not schemaaware
2069     */
2070    protected LdifEntry createLdifEntry( SchemaManager schemaManager )
2071    {
2072        if ( schemaManager != null )
2073        {
2074            return new LdifEntry( schemaManager );
2075        }
2076        else
2077        {
2078            return new LdifEntry();
2079        }
2080    }
2081
2082
2083    /**
2084     * @return true if the DN validation is turned on
2085     */
2086    public boolean isValidateDn()
2087    {
2088        return validateDn;
2089    }
2090
2091
2092    /**
2093     * Turns on/off the DN validation
2094     * 
2095     * @param validateDn the boolean flag
2096     */
2097    public void setValidateDn( boolean validateDn )
2098    {
2099        this.validateDn = validateDn;
2100    }
2101
2102
2103    /**
2104     * @param schemaManager the schemaManager to set
2105     */
2106    public void setSchemaManager( SchemaManager schemaManager )
2107    {
2108        this.schemaManager = schemaManager;
2109    }
2110
2111
2112    /**
2113     * {@inheritDoc}
2114     */
2115    public void close() throws IOException
2116    {
2117        if ( reader != null )
2118        {
2119            position = 0;
2120            reader.close();
2121            containsEntries = false;
2122            containsChanges = false;
2123            offset = entryOffset = lineNumber = 0;
2124        }
2125    }
2126}