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