001    /*
002     * Copyright 2007-2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2016 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldif;
022    
023    
024    
025    import java.io.Closeable;
026    import java.io.File;
027    import java.io.IOException;
028    import java.io.OutputStream;
029    import java.io.FileOutputStream;
030    import java.io.BufferedOutputStream;
031    import java.util.List;
032    import java.util.ArrayList;
033    import java.util.Arrays;
034    
035    import com.unboundid.asn1.ASN1OctetString;
036    import com.unboundid.ldap.sdk.Entry;
037    import com.unboundid.util.Base64;
038    import com.unboundid.util.LDAPSDKThreadFactory;
039    import com.unboundid.util.ByteStringBuffer;
040    import com.unboundid.util.parallel.ParallelProcessor;
041    import com.unboundid.util.parallel.Result;
042    import com.unboundid.util.parallel.Processor;
043    
044    import static com.unboundid.util.Debug.*;
045    import static com.unboundid.util.StaticUtils.*;
046    import static com.unboundid.util.Validator.*;
047    
048    
049    
050    /**
051     * This class provides an LDIF writer, which can be used to write entries and
052     * change records in the LDAP Data Interchange Format as per
053     * <A HREF="http://www.ietf.org/rfc/rfc2849.txt">RFC 2849</A>.
054     * <BR><BR>
055     * <H2>Example</H2>
056     * The following example performs a search to find all users in the "Sales"
057     * department and then writes their entries to an LDIF file:
058     * <PRE>
059     * // Perform a search to find all users who are members of the sales
060     * // department.
061     * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com",
062     *      SearchScope.SUB, Filter.createEqualityFilter("ou", "Sales"));
063     * SearchResult searchResult;
064     * try
065     * {
066     *   searchResult = connection.search(searchRequest);
067     * }
068     * catch (LDAPSearchException lse)
069     * {
070     *   searchResult = lse.getSearchResult();
071     * }
072     * LDAPTestUtils.assertResultCodeEquals(searchResult, ResultCode.SUCCESS);
073     *
074     * // Write all of the matching entries to LDIF.
075     * int entriesWritten = 0;
076     * LDIFWriter ldifWriter = new LDIFWriter(pathToLDIF);
077     * for (SearchResultEntry entry : searchResult.getSearchEntries())
078     * {
079     *   ldifWriter.writeEntry(entry);
080     *   entriesWritten++;
081     * }
082     *
083     * ldifWriter.close();
084     * </PRE>
085     */
086    public final class LDIFWriter
087           implements Closeable
088    {
089      /**
090       * Indicates whether LDIF records should include a comment above each
091       * base64-encoded value that attempts to provide an unencoded representation
092       * of that value (with special characters escaped).
093       */
094      private static volatile boolean commentAboutBase64EncodedValues = false;
095    
096    
097    
098      /**
099       * The bytes that comprise the LDIF version header.
100       */
101      private static final byte[] VERSION_1_HEADER_BYTES =
102           getBytes("version: 1" + EOL);
103    
104    
105    
106      /**
107       * The default buffer size (128KB) that will be used when writing LDIF data
108       * to the appropriate destination.
109       */
110      private static final int DEFAULT_BUFFER_SIZE = 128 * 1024;
111    
112    
113    
114      // The writer that will be used to actually write the data.
115      private final BufferedOutputStream writer;
116    
117      // The byte string buffer that will be used to convert LDIF records to LDIF.
118      // It will only be used when operating synchronously.
119      private final ByteStringBuffer buffer;
120    
121      // The translator to use for change records to be written, if any.
122      private final LDIFWriterChangeRecordTranslator changeRecordTranslator;
123    
124      // The translator to use for entries to be written, if any.
125      private final LDIFWriterEntryTranslator entryTranslator;
126    
127      // The column at which to wrap long lines.
128      private int wrapColumn = 0;
129    
130      // A pre-computed value that is two less than the wrap column.
131      private int wrapColumnMinusTwo = -2;
132    
133      // non-null if this writer was configured to use multiple threads when
134      // writing batches of entries.
135      private final ParallelProcessor<LDIFRecord,ByteStringBuffer>
136           toLdifBytesInvoker;
137    
138    
139    
140      /**
141       * Creates a new LDIF writer that will write entries to the provided file.
142       *
143       * @param  path  The path to the LDIF file to be written.  It must not be
144       *               {@code null}.
145       *
146       * @throws  IOException  If a problem occurs while opening the provided file
147       *                       for writing.
148       */
149      public LDIFWriter(final String path)
150             throws IOException
151      {
152        this(new FileOutputStream(path));
153      }
154    
155    
156    
157      /**
158       * Creates a new LDIF writer that will write entries to the provided file.
159       *
160       * @param  file  The LDIF file to be written.  It must not be {@code null}.
161       *
162       * @throws  IOException  If a problem occurs while opening the provided file
163       *                       for writing.
164       */
165      public LDIFWriter(final File file)
166             throws IOException
167      {
168        this(new FileOutputStream(file));
169      }
170    
171    
172    
173      /**
174       * Creates a new LDIF writer that will write entries to the provided output
175       * stream.
176       *
177       * @param  outputStream  The output stream to which the data is to be written.
178       *                       It must not be {@code null}.
179       */
180      public LDIFWriter(final OutputStream outputStream)
181      {
182        this(outputStream, 0);
183      }
184    
185    
186    
187      /**
188       * Creates a new LDIF writer that will write entries to the provided output
189       * stream optionally using parallelThreads when writing batches of LDIF
190       * records.
191       *
192       * @param  outputStream     The output stream to which the data is to be
193       *                          written.  It must not be {@code null}.
194       * @param  parallelThreads  If this value is greater than zero, then the
195       *                          specified number of threads will be used to
196       *                          encode entries before writing them to the output
197       *                          for the {@code writeLDIFRecords(List)} method.
198       *                          Note this is the only output method that will
199       *                          use multiple threads.
200       *                          This should only be set to greater than zero when
201       *                          performance analysis has demonstrated that writing
202       *                          the LDIF is a bottleneck.  The default
203       *                          synchronous processing is normally fast enough.
204       *                          There is no benefit in passing in a value
205       *                          greater than the number of processors in the
206       *                          system.  A value of zero implies the
207       *                          default behavior of reading and parsing LDIF
208       *                          records synchronously when one of the read
209       *                          methods is called.
210       */
211      public LDIFWriter(final OutputStream outputStream, final int parallelThreads)
212      {
213        this(outputStream, parallelThreads, null);
214      }
215    
216    
217    
218      /**
219       * Creates a new LDIF writer that will write entries to the provided output
220       * stream optionally using parallelThreads when writing batches of LDIF
221       * records.
222       *
223       * @param  outputStream     The output stream to which the data is to be
224       *                          written.  It must not be {@code null}.
225       * @param  parallelThreads  If this value is greater than zero, then the
226       *                          specified number of threads will be used to
227       *                          encode entries before writing them to the output
228       *                          for the {@code writeLDIFRecords(List)} method.
229       *                          Note this is the only output method that will
230       *                          use multiple threads.
231       *                          This should only be set to greater than zero when
232       *                          performance analysis has demonstrated that writing
233       *                          the LDIF is a bottleneck.  The default
234       *                          synchronous processing is normally fast enough.
235       *                          There is no benefit in passing in a value
236       *                          greater than the number of processors in the
237       *                          system.  A value of zero implies the
238       *                          default behavior of reading and parsing LDIF
239       *                          records synchronously when one of the read
240       *                          methods is called.
241       * @param  entryTranslator  An optional translator that will be used to alter
242       *                          entries before they are actually written.  This
243       *                          may be {@code null} if no translator is needed.
244       */
245      public LDIFWriter(final OutputStream outputStream, final int parallelThreads,
246                        final LDIFWriterEntryTranslator entryTranslator)
247      {
248        this(outputStream, parallelThreads, entryTranslator, null);
249      }
250    
251    
252    
253      /**
254       * Creates a new LDIF writer that will write entries to the provided output
255       * stream optionally using parallelThreads when writing batches of LDIF
256       * records.
257       *
258       * @param  outputStream            The output stream to which the data is to
259       *                                 be written.  It must not be {@code null}.
260       * @param  parallelThreads         If this value is greater than zero, then
261       *                                 the specified number of threads will be
262       *                                 used to encode entries before writing them
263       *                                 to the output for the
264       *                                 {@code writeLDIFRecords(List)} method.
265       *                                 Note this is the only output method that
266       *                                 will use multiple threads.  This should
267       *                                 only be set to greater than zero when
268       *                                 performance analysis has demonstrated that
269       *                                 writing the LDIF is a bottleneck.  The
270       *                                 default synchronous processing is normally
271       *                                 fast enough.  There is no benefit in
272       *                                 passing in a value greater than the number
273       *                                 of processors in the system.  A value of
274       *                                 zero implies the default behavior of
275       *                                 reading and parsing LDIF records
276       *                                 synchronously when one of the read methods
277       *                                 is called.
278       * @param  entryTranslator         An optional translator that will be used to
279       *                                 alter entries before they are actually
280       *                                 written.  This may be {@code null} if no
281       *                                 translator is needed.
282       * @param  changeRecordTranslator  An optional translator that will be used to
283       *                                 alter change records before they are
284       *                                 actually written.  This may be {@code null}
285       *                                 if no translator is needed.
286       */
287      public LDIFWriter(final OutputStream outputStream, final int parallelThreads,
288                  final LDIFWriterEntryTranslator entryTranslator,
289                  final LDIFWriterChangeRecordTranslator changeRecordTranslator)
290      {
291        ensureNotNull(outputStream);
292        ensureTrue(parallelThreads >= 0,
293             "LDIFWriter.parallelThreads must not be negative.");
294    
295        this.entryTranslator = entryTranslator;
296        this.changeRecordTranslator = changeRecordTranslator;
297        buffer = new ByteStringBuffer();
298    
299        if (outputStream instanceof BufferedOutputStream)
300        {
301          writer = (BufferedOutputStream) outputStream;
302        }
303        else
304        {
305          writer = new BufferedOutputStream(outputStream, DEFAULT_BUFFER_SIZE);
306        }
307    
308        if (parallelThreads == 0)
309        {
310          toLdifBytesInvoker = null;
311        }
312        else
313        {
314          final LDAPSDKThreadFactory threadFactory =
315               new LDAPSDKThreadFactory("LDIFWriter Worker", true, null);
316          toLdifBytesInvoker = new ParallelProcessor<LDIFRecord,ByteStringBuffer>(
317               new Processor<LDIFRecord,ByteStringBuffer>() {
318                 public ByteStringBuffer process(final LDIFRecord input)
319                        throws IOException
320                 {
321                   final LDIFRecord r;
322                   if ((entryTranslator != null) && (input instanceof Entry))
323                   {
324                     r = entryTranslator.translateEntryToWrite((Entry) input);
325                     if (r == null)
326                     {
327                       return null;
328                     }
329                   }
330                   else if ((changeRecordTranslator != null) &&
331                            (input instanceof LDIFChangeRecord))
332                   {
333                     r = changeRecordTranslator.translateChangeRecordToWrite(
334                          (LDIFChangeRecord) input);
335                     if (r == null)
336                     {
337                       return null;
338                     }
339                   }
340                   else
341                   {
342                     r = input;
343                   }
344    
345                   final ByteStringBuffer b = new ByteStringBuffer(200);
346                   r.toLDIF(b, wrapColumn);
347                   return b;
348                 }
349               }, threadFactory, parallelThreads, 5);
350        }
351      }
352    
353    
354    
355      /**
356       * Flushes the output stream used by this LDIF writer to ensure any buffered
357       * data is written out.
358       *
359       * @throws  IOException  If a problem occurs while attempting to flush the
360       *                       output stream.
361       */
362      public void flush()
363             throws IOException
364      {
365        writer.flush();
366      }
367    
368    
369    
370      /**
371       * Closes this LDIF writer and the underlying LDIF target.
372       *
373       * @throws  IOException  If a problem occurs while closing the underlying LDIF
374       *                       target.
375       */
376      public void close()
377             throws IOException
378      {
379        try
380        {
381          if (toLdifBytesInvoker != null)
382          {
383            try
384            {
385              toLdifBytesInvoker.shutdown();
386            }
387            catch (InterruptedException e)
388            {
389              debugException(e);
390            }
391          }
392        }
393        finally
394        {
395          writer.close();
396        }
397      }
398    
399    
400    
401      /**
402       * Retrieves the column at which to wrap long lines.
403       *
404       * @return  The column at which to wrap long lines, or zero to indicate that
405       *          long lines should not be wrapped.
406       */
407      public int getWrapColumn()
408      {
409        return wrapColumn;
410      }
411    
412    
413    
414      /**
415       * Specifies the column at which to wrap long lines.  A value of zero
416       * indicates that long lines should not be wrapped.
417       *
418       * @param  wrapColumn  The column at which to wrap long lines.
419       */
420      public void setWrapColumn(final int wrapColumn)
421      {
422        this.wrapColumn = wrapColumn;
423    
424        wrapColumnMinusTwo = wrapColumn - 2;
425      }
426    
427    
428    
429      /**
430       * Indicates whether the LDIF writer should generate comments that attempt to
431       * provide unencoded representations (with special characters escaped) of any
432       * base64-encoded values in entries and change records that are written by
433       * this writer.
434       *
435       * @return  {@code true} if the LDIF writer should generate comments that
436       *          attempt to provide unencoded representations of any base64-encoded
437       *          values, or {@code false} if not.
438       */
439      public static boolean commentAboutBase64EncodedValues()
440      {
441        return commentAboutBase64EncodedValues;
442      }
443    
444    
445    
446      /**
447       * Specifies whether the LDIF writer should generate comments that attempt to
448       * provide unencoded representations (with special characters escaped) of any
449       * base64-encoded values in entries and change records that are written by
450       * this writer.
451       *
452       * @param  commentAboutBase64EncodedValues  Indicates whether the LDIF writer
453       *                                          should generate comments that
454       *                                          attempt to provide unencoded
455       *                                          representations (with special
456       *                                          characters escaped) of any
457       *                                          base64-encoded values in entries
458       *                                          and change records that are
459       *                                          written by this writer.
460       */
461      public static void setCommentAboutBase64EncodedValues(
462                              final boolean commentAboutBase64EncodedValues)
463      {
464        LDIFWriter.commentAboutBase64EncodedValues =
465             commentAboutBase64EncodedValues;
466      }
467    
468    
469    
470      /**
471       * Writes the LDIF version header (i.e.,"version: 1").  If a version header
472       * is to be added to the LDIF content, it should be done before any entries or
473       * change records have been written.
474       *
475       * @throws  IOException  If a problem occurs while writing the version header.
476       */
477      public void writeVersionHeader()
478             throws IOException
479      {
480        writer.write(VERSION_1_HEADER_BYTES);
481      }
482    
483    
484    
485      /**
486       * Writes the provided entry in LDIF form.
487       *
488       * @param  entry  The entry to be written.  It must not be {@code null}.
489       *
490       * @throws  IOException  If a problem occurs while writing the LDIF data.
491       */
492      public void writeEntry(final Entry entry)
493             throws IOException
494      {
495        writeEntry(entry, null);
496      }
497    
498    
499    
500      /**
501       * Writes the provided entry in LDIF form, preceded by the provided comment.
502       *
503       * @param  entry    The entry to be written in LDIF form.  It must not be
504       *                  {@code null}.
505       * @param  comment  The comment to be written before the entry.  It may be
506       *                  {@code null} if no comment is to be written.
507       *
508       * @throws  IOException  If a problem occurs while writing the LDIF data.
509       */
510      public void writeEntry(final Entry entry, final String comment)
511             throws IOException
512      {
513        ensureNotNull(entry);
514    
515        final Entry e;
516        if (entryTranslator == null)
517        {
518          e = entry;
519        }
520        else
521        {
522          e = entryTranslator.translateEntryToWrite(entry);
523          if (e == null)
524          {
525            return;
526          }
527        }
528    
529        if (comment != null)
530        {
531          writeComment(comment, false, false);
532        }
533    
534        debugLDIFWrite(e);
535        writeLDIF(e);
536      }
537    
538    
539    
540      /**
541       * Writes the provided change record in LDIF form.
542       *
543       * @param  changeRecord  The change record to be written.  It must not be
544       *                       {@code null}.
545       *
546       * @throws  IOException  If a problem occurs while writing the LDIF data.
547       */
548      public void writeChangeRecord(final LDIFChangeRecord changeRecord)
549             throws IOException
550      {
551        writeChangeRecord(changeRecord, null);
552      }
553    
554    
555    
556      /**
557       * Writes the provided change record in LDIF form, preceded by the provided
558       * comment.
559       *
560       * @param  changeRecord  The change record to be written.  It must not be
561       *                       {@code null}.
562       * @param  comment       The comment to be written before the entry.  It may
563       *                       be {@code null} if no comment is to be written.
564       *
565       * @throws  IOException  If a problem occurs while writing the LDIF data.
566       */
567      public void writeChangeRecord(final LDIFChangeRecord changeRecord,
568                                    final String comment)
569             throws IOException
570      {
571        ensureNotNull(changeRecord);
572    
573        final LDIFChangeRecord r;
574        if (changeRecordTranslator == null)
575        {
576          r = changeRecord;
577        }
578        else
579        {
580          r = changeRecordTranslator.translateChangeRecordToWrite(changeRecord);
581          if (r == null)
582          {
583            return;
584          }
585        }
586    
587        if (comment != null)
588        {
589          writeComment(comment, false, false);
590        }
591    
592        debugLDIFWrite(r);
593        writeLDIF(r);
594      }
595    
596    
597    
598      /**
599       * Writes the provided record in LDIF form.
600       *
601       * @param  record  The LDIF record to be written.  It must not be
602       *                 {@code null}.
603       *
604       * @throws  IOException  If a problem occurs while writing the LDIF data.
605       */
606      public void writeLDIFRecord(final LDIFRecord record)
607             throws IOException
608      {
609        writeLDIFRecord(record, null);
610      }
611    
612    
613    
614      /**
615       * Writes the provided record in LDIF form, preceded by the provided comment.
616       *
617       * @param  record   The LDIF record to be written.  It must not be
618       *                  {@code null}.
619       * @param  comment  The comment to be written before the LDIF record.  It may
620       *                  be {@code null} if no comment is to be written.
621       *
622       * @throws  IOException  If a problem occurs while writing the LDIF data.
623       */
624      public void writeLDIFRecord(final LDIFRecord record, final String comment)
625             throws IOException
626      {
627        ensureNotNull(record);
628    
629        final LDIFRecord r;
630        if ((entryTranslator != null) && (record instanceof Entry))
631        {
632          r = entryTranslator.translateEntryToWrite((Entry) record);
633          if (r == null)
634          {
635            return;
636          }
637        }
638        else if ((changeRecordTranslator != null) &&
639                 (record instanceof LDIFChangeRecord))
640        {
641          r = changeRecordTranslator.translateChangeRecordToWrite(
642               (LDIFChangeRecord) record);
643          if (r == null)
644          {
645            return;
646          }
647        }
648        else
649        {
650          r = record;
651        }
652    
653        debugLDIFWrite(r);
654        if (comment != null)
655        {
656          writeComment(comment, false, false);
657        }
658    
659        writeLDIF(r);
660      }
661    
662    
663    
664      /**
665       * Writes the provided list of LDIF records (most likely Entries) to the
666       * output.  If this LDIFWriter was constructed without any parallel
667       * output threads, then this behaves identically to calling
668       * {@code writeLDIFRecord()} sequentially for each item in the list.
669       * If this LDIFWriter was constructed to write records in parallel, then
670       * the configured number of threads are used to convert the records to raw
671       * bytes, which are sequentially written to the input file.  This can speed up
672       * the total time to write a large set of records. Either way, the output
673       * records are guaranteed to be written in the order they appear in the list.
674       *
675       * @param ldifRecords  The LDIF records (most likely entries) to write to the
676       *                     output.
677       *
678       * @throws IOException  If a problem occurs while writing the LDIF data.
679       *
680       * @throws InterruptedException  If this thread is interrupted while waiting
681       *                               for the records to be written to the output.
682       */
683      public void writeLDIFRecords(final List<? extends LDIFRecord> ldifRecords)
684             throws IOException, InterruptedException
685      {
686        if (toLdifBytesInvoker == null)
687        {
688          for (final LDIFRecord ldifRecord : ldifRecords)
689          {
690            writeLDIFRecord(ldifRecord);
691          }
692        }
693        else
694        {
695          final List<Result<LDIFRecord,ByteStringBuffer>> results =
696               toLdifBytesInvoker.processAll(ldifRecords);
697          for (final Result<LDIFRecord,ByteStringBuffer> result: results)
698          {
699            rethrow(result.getFailureCause());
700    
701            final ByteStringBuffer encodedBytes = result.getOutput();
702            if (encodedBytes != null)
703            {
704              encodedBytes.write(writer);
705              writer.write(EOL_BYTES);
706            }
707          }
708        }
709      }
710    
711    
712    
713    
714      /**
715       * Writes the provided comment to the LDIF target, wrapping long lines as
716       * necessary.
717       *
718       * @param  comment      The comment to be written to the LDIF target.  It must
719       *                      not be {@code null}.
720       * @param  spaceBefore  Indicates whether to insert a blank line before the
721       *                      comment.
722       * @param  spaceAfter   Indicates whether to insert a blank line after the
723       *                      comment.
724       *
725       * @throws  IOException  If a problem occurs while writing the LDIF data.
726       */
727      public void writeComment(final String comment, final boolean spaceBefore,
728                               final boolean spaceAfter)
729             throws IOException
730      {
731        ensureNotNull(comment);
732        if (spaceBefore)
733        {
734          writer.write(EOL_BYTES);
735        }
736    
737        //
738        // Check for a newline explicitly to avoid the overhead of the regex
739        // for the common case of a single-line comment.
740        //
741    
742        if (comment.indexOf('\n') < 0)
743        {
744          writeSingleLineComment(comment);
745        }
746        else
747        {
748          //
749          // Split on blank lines and wrap each line individually.
750          //
751    
752          final String[] lines = comment.split("\\r?\\n");
753          for (final String line: lines)
754          {
755            writeSingleLineComment(line);
756          }
757        }
758    
759        if (spaceAfter)
760        {
761          writer.write(EOL_BYTES);
762        }
763      }
764    
765    
766    
767      /**
768       * Writes the provided comment to the LDIF target, wrapping long lines as
769       * necessary.
770       *
771       * @param  comment      The comment to be written to the LDIF target.  It must
772       *                      not be {@code null}, and it must not include any line
773       *                      breaks.
774       *
775       * @throws  IOException  If a problem occurs while writing the LDIF data.
776       */
777      private void writeSingleLineComment(final String comment)
778              throws IOException
779      {
780        // We will always wrap comments, even if we won't wrap LDIF entries.  If
781        // there is a wrap column set, then use it.  Otherwise use the terminal
782        // width and back off two characters for the "# " at the beginning.
783        final int commentWrapMinusTwo;
784        if (wrapColumn <= 0)
785        {
786          commentWrapMinusTwo = TERMINAL_WIDTH_COLUMNS - 3;
787        }
788        else
789        {
790          commentWrapMinusTwo = wrapColumnMinusTwo;
791        }
792    
793        buffer.clear();
794        final int length = comment.length();
795        if (length <= commentWrapMinusTwo)
796        {
797          buffer.append("# ");
798          buffer.append(comment);
799          buffer.append(EOL_BYTES);
800        }
801        else
802        {
803          int minPos = 0;
804          while (minPos < length)
805          {
806            if ((length - minPos) <= commentWrapMinusTwo)
807            {
808              buffer.append("# ");
809              buffer.append(comment.substring(minPos));
810              buffer.append(EOL_BYTES);
811              break;
812            }
813    
814            // First, adjust the position until we find a space.  Go backwards if
815            // possible, but if we can't find one there then go forward.
816            boolean spaceFound = false;
817            final int pos = minPos + commentWrapMinusTwo;
818            int     spacePos   = pos;
819            while (spacePos > minPos)
820            {
821              if (comment.charAt(spacePos) == ' ')
822              {
823                spaceFound = true;
824                break;
825              }
826    
827              spacePos--;
828            }
829    
830            if (! spaceFound)
831            {
832              spacePos = pos + 1;
833              while (spacePos < length)
834              {
835                if (comment.charAt(spacePos) == ' ')
836                {
837                  spaceFound = true;
838                  break;
839                }
840    
841                spacePos++;
842              }
843    
844              if (! spaceFound)
845              {
846                // There are no spaces at all in the remainder of the comment, so
847                // we'll just write the remainder of it all at once.
848                buffer.append("# ");
849                buffer.append(comment.substring(minPos));
850                buffer.append(EOL_BYTES);
851                break;
852              }
853            }
854    
855            // We have a space, so we'll write up to the space position and then
856            // start up after the next space.
857            buffer.append("# ");
858            buffer.append(comment.substring(minPos, spacePos));
859            buffer.append(EOL_BYTES);
860    
861            minPos = spacePos + 1;
862            while ((minPos < length) && (comment.charAt(minPos) == ' '))
863            {
864              minPos++;
865            }
866          }
867        }
868    
869        buffer.write(writer);
870      }
871    
872    
873    
874      /**
875       * Writes the provided record to the LDIF target, wrapping long lines as
876       * necessary.
877       *
878       * @param  record  The LDIF record to be written.
879       *
880       * @throws  IOException  If a problem occurs while writing the LDIF data.
881       */
882      private void writeLDIF(final LDIFRecord record)
883              throws IOException
884      {
885        buffer.clear();
886        record.toLDIF(buffer, wrapColumn);
887        buffer.append(EOL_BYTES);
888        buffer.write(writer);
889      }
890    
891    
892    
893      /**
894       * Performs any appropriate wrapping for the provided set of LDIF lines.
895       *
896       * @param  wrapColumn  The column at which to wrap long lines.  A value that
897       *                     is less than or equal to two indicates that no
898       *                     wrapping should be performed.
899       * @param  ldifLines   The set of lines that make up the LDIF data to be
900       *                     wrapped.
901       *
902       * @return  A new list of lines that have been wrapped as appropriate.
903       */
904      public static List<String> wrapLines(final int wrapColumn,
905                                           final String... ldifLines)
906      {
907        return wrapLines(wrapColumn, Arrays.asList(ldifLines));
908      }
909    
910    
911    
912      /**
913       * Performs any appropriate wrapping for the provided set of LDIF lines.
914       *
915       * @param  wrapColumn  The column at which to wrap long lines.  A value that
916       *                     is less than or equal to two indicates that no
917       *                     wrapping should be performed.
918       * @param  ldifLines   The set of lines that make up the LDIF data to be
919       *                     wrapped.
920       *
921       * @return  A new list of lines that have been wrapped as appropriate.
922       */
923      public static List<String> wrapLines(final int wrapColumn,
924                                           final List<String> ldifLines)
925      {
926        if (wrapColumn <= 2)
927        {
928          return new ArrayList<String>(ldifLines);
929        }
930    
931        final ArrayList<String> newLines = new ArrayList<String>(ldifLines.size());
932        for (final String s : ldifLines)
933        {
934          final int length = s.length();
935          if (length <= wrapColumn)
936          {
937            newLines.add(s);
938            continue;
939          }
940    
941          newLines.add(s.substring(0, wrapColumn));
942    
943          int pos = wrapColumn;
944          while (pos < length)
945          {
946            if ((length - pos + 1) <= wrapColumn)
947            {
948              newLines.add(' ' + s.substring(pos));
949              break;
950            }
951            else
952            {
953              newLines.add(' ' + s.substring(pos, (pos+wrapColumn-1)));
954              pos += wrapColumn - 1;
955            }
956          }
957        }
958    
959        return newLines;
960      }
961    
962    
963    
964      /**
965       * Creates a string consisting of the provided attribute name followed by
966       * either a single colon and the string representation of the provided value,
967       * or two colons and the base64-encoded representation of the provided value.
968       *
969       * @param  name   The name for the attribute.
970       * @param  value  The value for the attribute.
971       *
972       * @return  A string consisting of the provided attribute name followed by
973       *          either a single colon and the string representation of the
974       *          provided value, or two colons and the base64-encoded
975       *          representation of the provided value.
976       */
977      public static String encodeNameAndValue(final String name,
978                                              final ASN1OctetString value)
979      {
980        final StringBuilder buffer = new StringBuilder();
981        encodeNameAndValue(name, value, buffer);
982        return buffer.toString();
983      }
984    
985    
986    
987      /**
988       * Appends a string to the provided buffer consisting of the provided
989       * attribute name followed by either a single colon and the string
990       * representation of the provided value, or two colons and the base64-encoded
991       * representation of the provided value.
992       *
993       * @param  name    The name for the attribute.
994       * @param  value   The value for the attribute.
995       * @param  buffer  The buffer to which the name and value are to be written.
996       */
997      public static void encodeNameAndValue(final String name,
998                                            final ASN1OctetString value,
999                                            final StringBuilder buffer)
1000      {
1001        encodeNameAndValue(name, value, buffer, 0);
1002      }
1003    
1004    
1005    
1006      /**
1007       * Appends a string to the provided buffer consisting of the provided
1008       * attribute name followed by either a single colon and the string
1009       * representation of the provided value, or two colons and the base64-encoded
1010       * representation of the provided value.
1011       *
1012       * @param  name        The name for the attribute.
1013       * @param  value       The value for the attribute.
1014       * @param  buffer      The buffer to which the name and value are to be
1015       *                     written.
1016       * @param  wrapColumn  The column at which to wrap long lines.  A value that
1017       *                     is less than or equal to two indicates that no
1018       *                     wrapping should be performed.
1019       */
1020      public static void encodeNameAndValue(final String name,
1021                                            final ASN1OctetString value,
1022                                            final StringBuilder buffer,
1023                                            final int wrapColumn)
1024      {
1025        final int bufferStartPos = buffer.length();
1026        final byte[] valueBytes = value.getValue();
1027        boolean base64Encoded = false;
1028    
1029        try
1030        {
1031          buffer.append(name);
1032          buffer.append(':');
1033    
1034          final int length = valueBytes.length;
1035          if (length == 0)
1036          {
1037            buffer.append(' ');
1038            return;
1039          }
1040    
1041          // If the value starts with a space, colon, or less-than character, then
1042          // it must be base64-encoded.
1043          switch (valueBytes[0])
1044          {
1045            case ' ':
1046            case ':':
1047            case '<':
1048              buffer.append(": ");
1049              Base64.encode(valueBytes, buffer);
1050              base64Encoded = true;
1051              return;
1052          }
1053    
1054          // If the value ends with a space, then it should be base64-encoded.
1055          if (valueBytes[length-1] == ' ')
1056          {
1057            buffer.append(": ");
1058            Base64.encode(valueBytes, buffer);
1059            base64Encoded = true;
1060            return;
1061          }
1062    
1063          // If any character in the value is outside the ASCII range, or is the
1064          // NUL, LF, or CR character, then the value should be base64-encoded.
1065          for (int i=0; i < length; i++)
1066          {
1067            if ((valueBytes[i] & 0x7F) != (valueBytes[i] & 0xFF))
1068            {
1069              buffer.append(": ");
1070              Base64.encode(valueBytes, buffer);
1071              base64Encoded = true;
1072              return;
1073            }
1074    
1075            switch (valueBytes[i])
1076            {
1077              case 0x00:  // The NUL character
1078              case 0x0A:  // The LF character
1079              case 0x0D:  // The CR character
1080                buffer.append(": ");
1081                Base64.encode(valueBytes, buffer);
1082                base64Encoded = true;
1083                return;
1084            }
1085          }
1086    
1087          // If we've gotten here, then the string value is acceptable.
1088          buffer.append(' ');
1089          buffer.append(value.stringValue());
1090        }
1091        finally
1092        {
1093          if (wrapColumn > 2)
1094          {
1095            final int length = buffer.length() - bufferStartPos;
1096            if (length > wrapColumn)
1097            {
1098              final String EOL_PLUS_SPACE = EOL + ' ';
1099              buffer.insert((bufferStartPos+wrapColumn), EOL_PLUS_SPACE);
1100    
1101              int pos = bufferStartPos + (2*wrapColumn) +
1102                        EOL_PLUS_SPACE.length() - 1;
1103              while (pos < buffer.length())
1104              {
1105                buffer.insert(pos, EOL_PLUS_SPACE);
1106                pos += (wrapColumn - 1 + EOL_PLUS_SPACE.length());
1107              }
1108            }
1109          }
1110    
1111          if (base64Encoded && commentAboutBase64EncodedValues)
1112          {
1113            writeBase64DecodedValueComment(valueBytes, buffer, wrapColumn);
1114          }
1115        }
1116      }
1117    
1118    
1119    
1120      /**
1121       * Appends a comment to the provided buffer with an unencoded representation
1122       * of the provided value.  This will only have any effect if
1123       * {@code commentAboutBase64EncodedValues} is {@code true}.
1124       *
1125       * @param  valueBytes  The bytes that comprise the value.
1126       * @param  buffer      The buffer to which the comment should be appended.
1127       * @param  wrapColumn  The column at which to wrap long lines.
1128       */
1129      private static void writeBase64DecodedValueComment(final byte[] valueBytes,
1130                                                         final StringBuilder buffer,
1131                                                         final int wrapColumn)
1132      {
1133        if (commentAboutBase64EncodedValues)
1134        {
1135          final int wrapColumnMinusTwo;
1136          if (wrapColumn <= 5)
1137          {
1138            wrapColumnMinusTwo = TERMINAL_WIDTH_COLUMNS - 3;
1139          }
1140          else
1141          {
1142            wrapColumnMinusTwo = wrapColumn - 2;
1143          }
1144    
1145          final int wrapColumnMinusThree = wrapColumnMinusTwo - 1;
1146    
1147          boolean first = true;
1148          final String comment =
1149               "Non-base64-encoded representation of the above value: " +
1150                    getEscapedValue(valueBytes);
1151          for (final String s :
1152               wrapLine(comment, wrapColumnMinusTwo, wrapColumnMinusThree))
1153          {
1154            buffer.append(EOL);
1155            buffer.append("# ");
1156            if (first)
1157            {
1158              first = false;
1159            }
1160            else
1161            {
1162              buffer.append(' ');
1163            }
1164            buffer.append(s);
1165          }
1166        }
1167      }
1168    
1169    
1170    
1171      /**
1172       * Appends a string to the provided buffer consisting of the provided
1173       * attribute name followed by either a single colon and the string
1174       * representation of the provided value, or two colons and the base64-encoded
1175       * representation of the provided value.  It may optionally be wrapped at the
1176       * specified column.
1177       *
1178       * @param  name        The name for the attribute.
1179       * @param  value       The value for the attribute.
1180       * @param  buffer      The buffer to which the name and value are to be
1181       *                     written.
1182       * @param  wrapColumn  The column at which to wrap long lines.  A value that
1183       *                     is less than or equal to two indicates that no
1184       *                     wrapping should be performed.
1185       */
1186      public static void encodeNameAndValue(final String name,
1187                                            final ASN1OctetString value,
1188                                            final ByteStringBuffer buffer,
1189                                            final int wrapColumn)
1190      {
1191        final int bufferStartPos = buffer.length();
1192        boolean base64Encoded = false;
1193    
1194        try
1195        {
1196          buffer.append(name);
1197          base64Encoded = encodeValue(value, buffer);
1198        }
1199        finally
1200        {
1201          if (wrapColumn > 2)
1202          {
1203            final int length = buffer.length() - bufferStartPos;
1204            if (length > wrapColumn)
1205            {
1206              final byte[] EOL_BYTES_PLUS_SPACE = new byte[EOL_BYTES.length + 1];
1207              System.arraycopy(EOL_BYTES, 0, EOL_BYTES_PLUS_SPACE, 0,
1208                               EOL_BYTES.length);
1209              EOL_BYTES_PLUS_SPACE[EOL_BYTES.length] = ' ';
1210    
1211              buffer.insert((bufferStartPos+wrapColumn), EOL_BYTES_PLUS_SPACE);
1212    
1213              int pos = bufferStartPos + (2*wrapColumn) +
1214                        EOL_BYTES_PLUS_SPACE.length - 1;
1215              while (pos < buffer.length())
1216              {
1217                buffer.insert(pos, EOL_BYTES_PLUS_SPACE);
1218                pos += (wrapColumn - 1 + EOL_BYTES_PLUS_SPACE.length);
1219              }
1220            }
1221          }
1222    
1223          if (base64Encoded && commentAboutBase64EncodedValues)
1224          {
1225            writeBase64DecodedValueComment(value.getValue(), buffer, wrapColumn);
1226          }
1227        }
1228      }
1229    
1230    
1231    
1232      /**
1233       * Appends a string to the provided buffer consisting of the properly-encoded
1234       * representation of the provided value, including the necessary colon(s) and
1235       * space that precede it.  Depending on the content of the value, it will
1236       * either be used as-is or base64-encoded.
1237       *
1238       * @param  value   The value for the attribute.
1239       * @param  buffer  The buffer to which the value is to be written.
1240       *
1241       * @return  {@code true} if the value was base64-encoded, or {@code false} if
1242       *          not.
1243       */
1244      static boolean encodeValue(final ASN1OctetString value,
1245                                 final ByteStringBuffer buffer)
1246      {
1247        buffer.append(':');
1248    
1249        final byte[] valueBytes = value.getValue();
1250        final int length = valueBytes.length;
1251        if (length == 0)
1252        {
1253          buffer.append(' ');
1254          return false;
1255        }
1256    
1257        // If the value starts with a space, colon, or less-than character, then
1258        // it must be base64-encoded.
1259        switch (valueBytes[0])
1260        {
1261          case ' ':
1262          case ':':
1263          case '<':
1264            buffer.append(':');
1265            buffer.append(' ');
1266            Base64.encode(valueBytes, buffer);
1267            return true;
1268        }
1269    
1270        // If the value ends with a space, then it should be base64-encoded.
1271        if (valueBytes[length-1] == ' ')
1272        {
1273          buffer.append(':');
1274          buffer.append(' ');
1275          Base64.encode(valueBytes, buffer);
1276          return true;
1277        }
1278    
1279        // If any character in the value is outside the ASCII range, or is the
1280        // NUL, LF, or CR character, then the value should be base64-encoded.
1281        for (int i=0; i < length; i++)
1282        {
1283          if ((valueBytes[i] & 0x7F) != (valueBytes[i] & 0xFF))
1284          {
1285            buffer.append(':');
1286            buffer.append(' ');
1287            Base64.encode(valueBytes, buffer);
1288            return true;
1289          }
1290    
1291          switch (valueBytes[i])
1292          {
1293            case 0x00:  // The NUL character
1294            case 0x0A:  // The LF character
1295            case 0x0D:  // The CR character
1296              buffer.append(':');
1297              buffer.append(' ');
1298    
1299              Base64.encode(valueBytes, buffer);
1300              return true;
1301          }
1302        }
1303    
1304        // If we've gotten here, then the string value is acceptable.
1305        buffer.append(' ');
1306        buffer.append(valueBytes);
1307        return false;
1308      }
1309    
1310    
1311    
1312      /**
1313       * Appends a comment to the provided buffer with an unencoded representation
1314       * of the provided value.  This will only have any effect if
1315       * {@code commentAboutBase64EncodedValues} is {@code true}.
1316       *
1317       * @param  valueBytes  The bytes that comprise the value.
1318       * @param  buffer      The buffer to which the comment should be appended.
1319       * @param  wrapColumn  The column at which to wrap long lines.
1320       */
1321      private static void writeBase64DecodedValueComment(final byte[] valueBytes,
1322                               final ByteStringBuffer buffer,
1323                               final int wrapColumn)
1324      {
1325        if (commentAboutBase64EncodedValues)
1326        {
1327          final int wrapColumnMinusTwo;
1328          if (wrapColumn <= 5)
1329          {
1330            wrapColumnMinusTwo = TERMINAL_WIDTH_COLUMNS - 3;
1331          }
1332          else
1333          {
1334            wrapColumnMinusTwo = wrapColumn - 2;
1335          }
1336    
1337          final int wrapColumnMinusThree = wrapColumnMinusTwo - 1;
1338    
1339          boolean first = true;
1340          final String comment =
1341               "Non-base64-encoded representation of the above value: " +
1342                    getEscapedValue(valueBytes);
1343          for (final String s :
1344               wrapLine(comment, wrapColumnMinusTwo, wrapColumnMinusThree))
1345          {
1346            buffer.append(EOL);
1347            buffer.append("# ");
1348            if (first)
1349            {
1350              first = false;
1351            }
1352            else
1353            {
1354              buffer.append(' ');
1355            }
1356            buffer.append(s);
1357          }
1358        }
1359      }
1360    
1361    
1362    
1363      /**
1364       * Retrieves a string representation of the provided value with all special
1365       * characters escaped with backslashes.
1366       *
1367       * @param  valueBytes  The byte array containing the value to encode.
1368       *
1369       * @return  A string representation of the provided value with any special
1370       *          characters
1371       */
1372      private static String getEscapedValue(final byte[] valueBytes)
1373      {
1374        final StringBuilder buffer = new StringBuilder(valueBytes.length * 2);
1375        for (int i=0; i < valueBytes.length; i++)
1376        {
1377          final byte b = valueBytes[i];
1378          switch (b)
1379          {
1380            case '\n':
1381              buffer.append("\\n");
1382              break;
1383            case '\r':
1384              buffer.append("\\r");
1385              break;
1386            case '\t':
1387              buffer.append("\\t");
1388              break;
1389            case ' ':
1390              if (i == 0)
1391              {
1392                buffer.append("\\ ");
1393              }
1394              else if ( i == (valueBytes.length - 1))
1395              {
1396                buffer.append("\\20");
1397              }
1398              else
1399              {
1400                buffer.append(' ');
1401              }
1402              break;
1403            case '<':
1404              if (i == 0)
1405              {
1406                buffer.append('\\');
1407              }
1408              buffer.append('<');
1409              break;
1410            case ':':
1411              if (i == 0)
1412              {
1413                buffer.append('\\');
1414              }
1415              buffer.append(':');
1416              break;
1417            default:
1418              if ((b >= '!') && (b <= '~'))
1419              {
1420                buffer.append((char) b);
1421              }
1422              else
1423              {
1424                buffer.append("\\");
1425                toHex(b, buffer);
1426              }
1427              break;
1428          }
1429        }
1430    
1431        return buffer.toString();
1432      }
1433    
1434    
1435    
1436      /**
1437       * If the provided exception is non-null, then it will be rethrown as an
1438       * unchecked exception or an IOException.
1439       *
1440       * @param t  The exception to rethrow as an an unchecked exception or an
1441       *           IOException or {@code null} if none.
1442       *
1443       * @throws IOException  If t is a checked exception.
1444       */
1445      static void rethrow(final Throwable t)
1446             throws IOException
1447      {
1448        if (t == null)
1449        {
1450          return;
1451        }
1452    
1453        if (t instanceof IOException)
1454        {
1455          throw (IOException) t;
1456        }
1457        else if (t instanceof RuntimeException)
1458        {
1459          throw (RuntimeException) t;
1460        }
1461        else if (t instanceof Error)
1462        {
1463          throw (Error) t;
1464        }
1465        else
1466        {
1467          throw createIOExceptionWithCause(null, t);
1468        }
1469      }
1470    }