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