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