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