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