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