001    /*
002     * Copyright 2007-2017 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2017 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              Thread.currentThread().interrupt();
394            }
395          }
396        }
397        finally
398        {
399          writer.close();
400        }
401      }
402    
403    
404    
405      /**
406       * Retrieves the column at which to wrap long lines.
407       *
408       * @return  The column at which to wrap long lines, or zero to indicate that
409       *          long lines should not be wrapped.
410       */
411      public int getWrapColumn()
412      {
413        return wrapColumn;
414      }
415    
416    
417    
418      /**
419       * Specifies the column at which to wrap long lines.  A value of zero
420       * indicates that long lines should not be wrapped.
421       *
422       * @param  wrapColumn  The column at which to wrap long lines.
423       */
424      public void setWrapColumn(final int wrapColumn)
425      {
426        this.wrapColumn = wrapColumn;
427    
428        wrapColumnMinusTwo = wrapColumn - 2;
429      }
430    
431    
432    
433      /**
434       * Indicates whether the LDIF writer should generate comments that attempt to
435       * provide unencoded representations (with special characters escaped) of any
436       * base64-encoded values in entries and change records that are written by
437       * this writer.
438       *
439       * @return  {@code true} if the LDIF writer should generate comments that
440       *          attempt to provide unencoded representations of any base64-encoded
441       *          values, or {@code false} if not.
442       */
443      public static boolean commentAboutBase64EncodedValues()
444      {
445        return commentAboutBase64EncodedValues;
446      }
447    
448    
449    
450      /**
451       * Specifies whether the LDIF writer should generate comments that attempt to
452       * provide unencoded representations (with special characters escaped) of any
453       * base64-encoded values in entries and change records that are written by
454       * this writer.
455       *
456       * @param  commentAboutBase64EncodedValues  Indicates whether the LDIF writer
457       *                                          should generate comments that
458       *                                          attempt to provide unencoded
459       *                                          representations (with special
460       *                                          characters escaped) of any
461       *                                          base64-encoded values in entries
462       *                                          and change records that are
463       *                                          written by this writer.
464       */
465      public static void setCommentAboutBase64EncodedValues(
466                              final boolean commentAboutBase64EncodedValues)
467      {
468        LDIFWriter.commentAboutBase64EncodedValues =
469             commentAboutBase64EncodedValues;
470      }
471    
472    
473    
474      /**
475       * Writes the LDIF version header (i.e.,"version: 1").  If a version header
476       * is to be added to the LDIF content, it should be done before any entries or
477       * change records have been written.
478       *
479       * @throws  IOException  If a problem occurs while writing the version header.
480       */
481      public void writeVersionHeader()
482             throws IOException
483      {
484        writer.write(VERSION_1_HEADER_BYTES);
485      }
486    
487    
488    
489      /**
490       * Writes the provided entry in LDIF form.
491       *
492       * @param  entry  The entry to be written.  It must not be {@code null}.
493       *
494       * @throws  IOException  If a problem occurs while writing the LDIF data.
495       */
496      public void writeEntry(final Entry entry)
497             throws IOException
498      {
499        writeEntry(entry, null);
500      }
501    
502    
503    
504      /**
505       * Writes the provided entry in LDIF form, preceded by the provided comment.
506       *
507       * @param  entry    The entry to be written in LDIF form.  It must not be
508       *                  {@code null}.
509       * @param  comment  The comment to be written before the entry.  It may be
510       *                  {@code null} if no comment is to be written.
511       *
512       * @throws  IOException  If a problem occurs while writing the LDIF data.
513       */
514      public void writeEntry(final Entry entry, final String comment)
515             throws IOException
516      {
517        ensureNotNull(entry);
518    
519        final Entry e;
520        if (entryTranslator == null)
521        {
522          e = entry;
523        }
524        else
525        {
526          e = entryTranslator.translateEntryToWrite(entry);
527          if (e == null)
528          {
529            return;
530          }
531        }
532    
533        if (comment != null)
534        {
535          writeComment(comment, false, false);
536        }
537    
538        debugLDIFWrite(e);
539        writeLDIF(e);
540      }
541    
542    
543    
544      /**
545       * Writes the provided change record in LDIF form.
546       *
547       * @param  changeRecord  The change record to be written.  It must not be
548       *                       {@code null}.
549       *
550       * @throws  IOException  If a problem occurs while writing the LDIF data.
551       */
552      public void writeChangeRecord(final LDIFChangeRecord changeRecord)
553             throws IOException
554      {
555        writeChangeRecord(changeRecord, null);
556      }
557    
558    
559    
560      /**
561       * Writes the provided change record in LDIF form, preceded by the provided
562       * comment.
563       *
564       * @param  changeRecord  The change record to be written.  It must not be
565       *                       {@code null}.
566       * @param  comment       The comment to be written before the entry.  It may
567       *                       be {@code null} if no comment is to be written.
568       *
569       * @throws  IOException  If a problem occurs while writing the LDIF data.
570       */
571      public void writeChangeRecord(final LDIFChangeRecord changeRecord,
572                                    final String comment)
573             throws IOException
574      {
575        ensureNotNull(changeRecord);
576    
577        final LDIFChangeRecord r;
578        if (changeRecordTranslator == null)
579        {
580          r = changeRecord;
581        }
582        else
583        {
584          r = changeRecordTranslator.translateChangeRecordToWrite(changeRecord);
585          if (r == null)
586          {
587            return;
588          }
589        }
590    
591        if (comment != null)
592        {
593          writeComment(comment, false, false);
594        }
595    
596        debugLDIFWrite(r);
597        writeLDIF(r);
598      }
599    
600    
601    
602      /**
603       * Writes the provided record in LDIF form.
604       *
605       * @param  record  The LDIF record to be written.  It must not be
606       *                 {@code null}.
607       *
608       * @throws  IOException  If a problem occurs while writing the LDIF data.
609       */
610      public void writeLDIFRecord(final LDIFRecord record)
611             throws IOException
612      {
613        writeLDIFRecord(record, null);
614      }
615    
616    
617    
618      /**
619       * Writes the provided record in LDIF form, preceded by the provided comment.
620       *
621       * @param  record   The LDIF record to be written.  It must not be
622       *                  {@code null}.
623       * @param  comment  The comment to be written before the LDIF record.  It may
624       *                  be {@code null} if no comment is to be written.
625       *
626       * @throws  IOException  If a problem occurs while writing the LDIF data.
627       */
628      public void writeLDIFRecord(final LDIFRecord record, final String comment)
629             throws IOException
630      {
631        ensureNotNull(record);
632    
633        final LDIFRecord r;
634        if ((entryTranslator != null) && (record instanceof Entry))
635        {
636          r = entryTranslator.translateEntryToWrite((Entry) record);
637          if (r == null)
638          {
639            return;
640          }
641        }
642        else if ((changeRecordTranslator != null) &&
643                 (record instanceof LDIFChangeRecord))
644        {
645          r = changeRecordTranslator.translateChangeRecordToWrite(
646               (LDIFChangeRecord) record);
647          if (r == null)
648          {
649            return;
650          }
651        }
652        else
653        {
654          r = record;
655        }
656    
657        debugLDIFWrite(r);
658        if (comment != null)
659        {
660          writeComment(comment, false, false);
661        }
662    
663        writeLDIF(r);
664      }
665    
666    
667    
668      /**
669       * Writes the provided list of LDIF records (most likely Entries) to the
670       * output.  If this LDIFWriter was constructed without any parallel
671       * output threads, then this behaves identically to calling
672       * {@code writeLDIFRecord()} sequentially for each item in the list.
673       * If this LDIFWriter was constructed to write records in parallel, then
674       * the configured number of threads are used to convert the records to raw
675       * bytes, which are sequentially written to the input file.  This can speed up
676       * the total time to write a large set of records. Either way, the output
677       * records are guaranteed to be written in the order they appear in the list.
678       *
679       * @param ldifRecords  The LDIF records (most likely entries) to write to the
680       *                     output.
681       *
682       * @throws IOException  If a problem occurs while writing the LDIF data.
683       *
684       * @throws InterruptedException  If this thread is interrupted while waiting
685       *                               for the records to be written to the output.
686       */
687      public void writeLDIFRecords(final List<? extends LDIFRecord> ldifRecords)
688             throws IOException, InterruptedException
689      {
690        if (toLdifBytesInvoker == null)
691        {
692          for (final LDIFRecord ldifRecord : ldifRecords)
693          {
694            writeLDIFRecord(ldifRecord);
695          }
696        }
697        else
698        {
699          final List<Result<LDIFRecord,ByteStringBuffer>> results =
700               toLdifBytesInvoker.processAll(ldifRecords);
701          for (final Result<LDIFRecord,ByteStringBuffer> result: results)
702          {
703            rethrow(result.getFailureCause());
704    
705            final ByteStringBuffer encodedBytes = result.getOutput();
706            if (encodedBytes != null)
707            {
708              encodedBytes.write(writer);
709              writer.write(EOL_BYTES);
710            }
711          }
712        }
713      }
714    
715    
716    
717    
718      /**
719       * Writes the provided comment to the LDIF target, wrapping long lines as
720       * necessary.
721       *
722       * @param  comment      The comment to be written to the LDIF target.  It must
723       *                      not be {@code null}.
724       * @param  spaceBefore  Indicates whether to insert a blank line before the
725       *                      comment.
726       * @param  spaceAfter   Indicates whether to insert a blank line after the
727       *                      comment.
728       *
729       * @throws  IOException  If a problem occurs while writing the LDIF data.
730       */
731      public void writeComment(final String comment, final boolean spaceBefore,
732                               final boolean spaceAfter)
733             throws IOException
734      {
735        ensureNotNull(comment);
736        if (spaceBefore)
737        {
738          writer.write(EOL_BYTES);
739        }
740    
741        //
742        // Check for a newline explicitly to avoid the overhead of the regex
743        // for the common case of a single-line comment.
744        //
745    
746        if (comment.indexOf('\n') < 0)
747        {
748          writeSingleLineComment(comment);
749        }
750        else
751        {
752          //
753          // Split on blank lines and wrap each line individually.
754          //
755    
756          final String[] lines = comment.split("\\r?\\n");
757          for (final String line: lines)
758          {
759            writeSingleLineComment(line);
760          }
761        }
762    
763        if (spaceAfter)
764        {
765          writer.write(EOL_BYTES);
766        }
767      }
768    
769    
770    
771      /**
772       * Writes the provided comment to the LDIF target, wrapping long lines as
773       * necessary.
774       *
775       * @param  comment      The comment to be written to the LDIF target.  It must
776       *                      not be {@code null}, and it must not include any line
777       *                      breaks.
778       *
779       * @throws  IOException  If a problem occurs while writing the LDIF data.
780       */
781      private void writeSingleLineComment(final String comment)
782              throws IOException
783      {
784        // We will always wrap comments, even if we won't wrap LDIF entries.  If
785        // there is a wrap column set, then use it.  Otherwise use the terminal
786        // width and back off two characters for the "# " at the beginning.
787        final int commentWrapMinusTwo;
788        if (wrapColumn <= 0)
789        {
790          commentWrapMinusTwo = TERMINAL_WIDTH_COLUMNS - 3;
791        }
792        else
793        {
794          commentWrapMinusTwo = wrapColumnMinusTwo;
795        }
796    
797        buffer.clear();
798        final int length = comment.length();
799        if (length <= commentWrapMinusTwo)
800        {
801          buffer.append("# ");
802          buffer.append(comment);
803          buffer.append(EOL_BYTES);
804        }
805        else
806        {
807          int minPos = 0;
808          while (minPos < length)
809          {
810            if ((length - minPos) <= commentWrapMinusTwo)
811            {
812              buffer.append("# ");
813              buffer.append(comment.substring(minPos));
814              buffer.append(EOL_BYTES);
815              break;
816            }
817    
818            // First, adjust the position until we find a space.  Go backwards if
819            // possible, but if we can't find one there then go forward.
820            boolean spaceFound = false;
821            final int pos = minPos + commentWrapMinusTwo;
822            int     spacePos   = pos;
823            while (spacePos > minPos)
824            {
825              if (comment.charAt(spacePos) == ' ')
826              {
827                spaceFound = true;
828                break;
829              }
830    
831              spacePos--;
832            }
833    
834            if (! spaceFound)
835            {
836              spacePos = pos + 1;
837              while (spacePos < length)
838              {
839                if (comment.charAt(spacePos) == ' ')
840                {
841                  spaceFound = true;
842                  break;
843                }
844    
845                spacePos++;
846              }
847    
848              if (! spaceFound)
849              {
850                // There are no spaces at all in the remainder of the comment, so
851                // we'll just write the remainder of it all at once.
852                buffer.append("# ");
853                buffer.append(comment.substring(minPos));
854                buffer.append(EOL_BYTES);
855                break;
856              }
857            }
858    
859            // We have a space, so we'll write up to the space position and then
860            // start up after the next space.
861            buffer.append("# ");
862            buffer.append(comment.substring(minPos, spacePos));
863            buffer.append(EOL_BYTES);
864    
865            minPos = spacePos + 1;
866            while ((minPos < length) && (comment.charAt(minPos) == ' '))
867            {
868              minPos++;
869            }
870          }
871        }
872    
873        buffer.write(writer);
874      }
875    
876    
877    
878      /**
879       * Writes the provided record to the LDIF target, wrapping long lines as
880       * necessary.
881       *
882       * @param  record  The LDIF record to be written.
883       *
884       * @throws  IOException  If a problem occurs while writing the LDIF data.
885       */
886      private void writeLDIF(final LDIFRecord record)
887              throws IOException
888      {
889        buffer.clear();
890        record.toLDIF(buffer, wrapColumn);
891        buffer.append(EOL_BYTES);
892        buffer.write(writer);
893      }
894    
895    
896    
897      /**
898       * Performs any appropriate wrapping for the provided set of LDIF lines.
899       *
900       * @param  wrapColumn  The column at which to wrap long lines.  A value that
901       *                     is less than or equal to two indicates that no
902       *                     wrapping should be performed.
903       * @param  ldifLines   The set of lines that make up the LDIF data to be
904       *                     wrapped.
905       *
906       * @return  A new list of lines that have been wrapped as appropriate.
907       */
908      public static List<String> wrapLines(final int wrapColumn,
909                                           final String... ldifLines)
910      {
911        return wrapLines(wrapColumn, Arrays.asList(ldifLines));
912      }
913    
914    
915    
916      /**
917       * Performs any appropriate wrapping for the provided set of LDIF lines.
918       *
919       * @param  wrapColumn  The column at which to wrap long lines.  A value that
920       *                     is less than or equal to two indicates that no
921       *                     wrapping should be performed.
922       * @param  ldifLines   The set of lines that make up the LDIF data to be
923       *                     wrapped.
924       *
925       * @return  A new list of lines that have been wrapped as appropriate.
926       */
927      public static List<String> wrapLines(final int wrapColumn,
928                                           final List<String> ldifLines)
929      {
930        if (wrapColumn <= 2)
931        {
932          return new ArrayList<String>(ldifLines);
933        }
934    
935        final ArrayList<String> newLines = new ArrayList<String>(ldifLines.size());
936        for (final String s : ldifLines)
937        {
938          final int length = s.length();
939          if (length <= wrapColumn)
940          {
941            newLines.add(s);
942            continue;
943          }
944    
945          newLines.add(s.substring(0, wrapColumn));
946    
947          int pos = wrapColumn;
948          while (pos < length)
949          {
950            if ((length - pos + 1) <= wrapColumn)
951            {
952              newLines.add(' ' + s.substring(pos));
953              break;
954            }
955            else
956            {
957              newLines.add(' ' + s.substring(pos, (pos+wrapColumn-1)));
958              pos += wrapColumn - 1;
959            }
960          }
961        }
962    
963        return newLines;
964      }
965    
966    
967    
968      /**
969       * Creates a string consisting of the provided attribute name followed by
970       * either a single colon and the string representation of the provided value,
971       * or two colons and the base64-encoded representation of the provided value.
972       *
973       * @param  name   The name for the attribute.
974       * @param  value  The value for the attribute.
975       *
976       * @return  A string consisting of the provided attribute name followed by
977       *          either a single colon and the string representation of the
978       *          provided value, or two colons and the base64-encoded
979       *          representation of the provided value.
980       */
981      public static String encodeNameAndValue(final String name,
982                                              final ASN1OctetString value)
983      {
984        final StringBuilder buffer = new StringBuilder();
985        encodeNameAndValue(name, value, buffer);
986        return buffer.toString();
987      }
988    
989    
990    
991      /**
992       * Appends a string to the provided buffer consisting of the provided
993       * attribute name followed by either a single colon and the string
994       * representation of the provided value, or two colons and the base64-encoded
995       * representation of the provided value.
996       *
997       * @param  name    The name for the attribute.
998       * @param  value   The value for the attribute.
999       * @param  buffer  The buffer to which the name and value are to be written.
1000       */
1001      public static void encodeNameAndValue(final String name,
1002                                            final ASN1OctetString value,
1003                                            final StringBuilder buffer)
1004      {
1005        encodeNameAndValue(name, value, buffer, 0);
1006      }
1007    
1008    
1009    
1010      /**
1011       * Appends a string to the provided buffer consisting of the provided
1012       * attribute name followed by either a single colon and the string
1013       * representation of the provided value, or two colons and the base64-encoded
1014       * representation of the provided value.
1015       *
1016       * @param  name        The name for the attribute.
1017       * @param  value       The value for the attribute.
1018       * @param  buffer      The buffer to which the name and value are to be
1019       *                     written.
1020       * @param  wrapColumn  The column at which to wrap long lines.  A value that
1021       *                     is less than or equal to two indicates that no
1022       *                     wrapping should be performed.
1023       */
1024      public static void encodeNameAndValue(final String name,
1025                                            final ASN1OctetString value,
1026                                            final StringBuilder buffer,
1027                                            final int wrapColumn)
1028      {
1029        final int bufferStartPos = buffer.length();
1030        final byte[] valueBytes = value.getValue();
1031        boolean base64Encoded = false;
1032    
1033        try
1034        {
1035          buffer.append(name);
1036          buffer.append(':');
1037    
1038          final int length = valueBytes.length;
1039          if (length == 0)
1040          {
1041            buffer.append(' ');
1042            return;
1043          }
1044    
1045          // If the value starts with a space, colon, or less-than character, then
1046          // it must be base64-encoded.
1047          switch (valueBytes[0])
1048          {
1049            case ' ':
1050            case ':':
1051            case '<':
1052              buffer.append(": ");
1053              Base64.encode(valueBytes, buffer);
1054              base64Encoded = true;
1055              return;
1056          }
1057    
1058          // If the value ends with a space, then it should be base64-encoded.
1059          if (valueBytes[length-1] == ' ')
1060          {
1061            buffer.append(": ");
1062            Base64.encode(valueBytes, buffer);
1063            base64Encoded = true;
1064            return;
1065          }
1066    
1067          // If any character in the value is outside the ASCII range, or is the
1068          // NUL, LF, or CR character, then the value should be base64-encoded.
1069          for (int i=0; i < length; i++)
1070          {
1071            if ((valueBytes[i] & 0x7F) != (valueBytes[i] & 0xFF))
1072            {
1073              buffer.append(": ");
1074              Base64.encode(valueBytes, buffer);
1075              base64Encoded = true;
1076              return;
1077            }
1078    
1079            switch (valueBytes[i])
1080            {
1081              case 0x00:  // The NUL character
1082              case 0x0A:  // The LF character
1083              case 0x0D:  // The CR character
1084                buffer.append(": ");
1085                Base64.encode(valueBytes, buffer);
1086                base64Encoded = true;
1087                return;
1088            }
1089          }
1090    
1091          // If we've gotten here, then the string value is acceptable.
1092          buffer.append(' ');
1093          buffer.append(value.stringValue());
1094        }
1095        finally
1096        {
1097          if (wrapColumn > 2)
1098          {
1099            final int length = buffer.length() - bufferStartPos;
1100            if (length > wrapColumn)
1101            {
1102              final String EOL_PLUS_SPACE = EOL + ' ';
1103              buffer.insert((bufferStartPos+wrapColumn), EOL_PLUS_SPACE);
1104    
1105              int pos = bufferStartPos + (2*wrapColumn) +
1106                        EOL_PLUS_SPACE.length() - 1;
1107              while (pos < buffer.length())
1108              {
1109                buffer.insert(pos, EOL_PLUS_SPACE);
1110                pos += (wrapColumn - 1 + EOL_PLUS_SPACE.length());
1111              }
1112            }
1113          }
1114    
1115          if (base64Encoded && commentAboutBase64EncodedValues)
1116          {
1117            writeBase64DecodedValueComment(valueBytes, buffer, wrapColumn);
1118          }
1119        }
1120      }
1121    
1122    
1123    
1124      /**
1125       * Appends a comment to the provided buffer with an unencoded representation
1126       * of the provided value.  This will only have any effect if
1127       * {@code commentAboutBase64EncodedValues} is {@code true}.
1128       *
1129       * @param  valueBytes  The bytes that comprise the value.
1130       * @param  buffer      The buffer to which the comment should be appended.
1131       * @param  wrapColumn  The column at which to wrap long lines.
1132       */
1133      private static void writeBase64DecodedValueComment(final byte[] valueBytes,
1134                                                         final StringBuilder buffer,
1135                                                         final int wrapColumn)
1136      {
1137        if (commentAboutBase64EncodedValues)
1138        {
1139          final int wrapColumnMinusTwo;
1140          if (wrapColumn <= 5)
1141          {
1142            wrapColumnMinusTwo = TERMINAL_WIDTH_COLUMNS - 3;
1143          }
1144          else
1145          {
1146            wrapColumnMinusTwo = wrapColumn - 2;
1147          }
1148    
1149          final int wrapColumnMinusThree = wrapColumnMinusTwo - 1;
1150    
1151          boolean first = true;
1152          final String comment =
1153               "Non-base64-encoded representation of the above value: " +
1154                    getEscapedValue(valueBytes);
1155          for (final String s :
1156               wrapLine(comment, wrapColumnMinusTwo, wrapColumnMinusThree))
1157          {
1158            buffer.append(EOL);
1159            buffer.append("# ");
1160            if (first)
1161            {
1162              first = false;
1163            }
1164            else
1165            {
1166              buffer.append(' ');
1167            }
1168            buffer.append(s);
1169          }
1170        }
1171      }
1172    
1173    
1174    
1175      /**
1176       * Appends a string to the provided buffer consisting of the provided
1177       * attribute name followed by either a single colon and the string
1178       * representation of the provided value, or two colons and the base64-encoded
1179       * representation of the provided value.  It may optionally be wrapped at the
1180       * specified column.
1181       *
1182       * @param  name        The name for the attribute.
1183       * @param  value       The value for the attribute.
1184       * @param  buffer      The buffer to which the name and value are to be
1185       *                     written.
1186       * @param  wrapColumn  The column at which to wrap long lines.  A value that
1187       *                     is less than or equal to two indicates that no
1188       *                     wrapping should be performed.
1189       */
1190      public static void encodeNameAndValue(final String name,
1191                                            final ASN1OctetString value,
1192                                            final ByteStringBuffer buffer,
1193                                            final int wrapColumn)
1194      {
1195        final int bufferStartPos = buffer.length();
1196        boolean base64Encoded = false;
1197    
1198        try
1199        {
1200          buffer.append(name);
1201          base64Encoded = encodeValue(value, buffer);
1202        }
1203        finally
1204        {
1205          if (wrapColumn > 2)
1206          {
1207            final int length = buffer.length() - bufferStartPos;
1208            if (length > wrapColumn)
1209            {
1210              final byte[] EOL_BYTES_PLUS_SPACE = new byte[EOL_BYTES.length + 1];
1211              System.arraycopy(EOL_BYTES, 0, EOL_BYTES_PLUS_SPACE, 0,
1212                               EOL_BYTES.length);
1213              EOL_BYTES_PLUS_SPACE[EOL_BYTES.length] = ' ';
1214    
1215              buffer.insert((bufferStartPos+wrapColumn), EOL_BYTES_PLUS_SPACE);
1216    
1217              int pos = bufferStartPos + (2*wrapColumn) +
1218                        EOL_BYTES_PLUS_SPACE.length - 1;
1219              while (pos < buffer.length())
1220              {
1221                buffer.insert(pos, EOL_BYTES_PLUS_SPACE);
1222                pos += (wrapColumn - 1 + EOL_BYTES_PLUS_SPACE.length);
1223              }
1224            }
1225          }
1226    
1227          if (base64Encoded && commentAboutBase64EncodedValues)
1228          {
1229            writeBase64DecodedValueComment(value.getValue(), buffer, wrapColumn);
1230          }
1231        }
1232      }
1233    
1234    
1235    
1236      /**
1237       * Appends a string to the provided buffer consisting of the properly-encoded
1238       * representation of the provided value, including the necessary colon(s) and
1239       * space that precede it.  Depending on the content of the value, it will
1240       * either be used as-is or base64-encoded.
1241       *
1242       * @param  value   The value for the attribute.
1243       * @param  buffer  The buffer to which the value is to be written.
1244       *
1245       * @return  {@code true} if the value was base64-encoded, or {@code false} if
1246       *          not.
1247       */
1248      static boolean encodeValue(final ASN1OctetString value,
1249                                 final ByteStringBuffer buffer)
1250      {
1251        buffer.append(':');
1252    
1253        final byte[] valueBytes = value.getValue();
1254        final int length = valueBytes.length;
1255        if (length == 0)
1256        {
1257          buffer.append(' ');
1258          return false;
1259        }
1260    
1261        // If the value starts with a space, colon, or less-than character, then
1262        // it must be base64-encoded.
1263        switch (valueBytes[0])
1264        {
1265          case ' ':
1266          case ':':
1267          case '<':
1268            buffer.append(':');
1269            buffer.append(' ');
1270            Base64.encode(valueBytes, buffer);
1271            return true;
1272        }
1273    
1274        // If the value ends with a space, then it should be base64-encoded.
1275        if (valueBytes[length-1] == ' ')
1276        {
1277          buffer.append(':');
1278          buffer.append(' ');
1279          Base64.encode(valueBytes, buffer);
1280          return true;
1281        }
1282    
1283        // If any character in the value is outside the ASCII range, or is the
1284        // NUL, LF, or CR character, then the value should be base64-encoded.
1285        for (int i=0; i < length; i++)
1286        {
1287          if ((valueBytes[i] & 0x7F) != (valueBytes[i] & 0xFF))
1288          {
1289            buffer.append(':');
1290            buffer.append(' ');
1291            Base64.encode(valueBytes, buffer);
1292            return true;
1293          }
1294    
1295          switch (valueBytes[i])
1296          {
1297            case 0x00:  // The NUL character
1298            case 0x0A:  // The LF character
1299            case 0x0D:  // The CR character
1300              buffer.append(':');
1301              buffer.append(' ');
1302    
1303              Base64.encode(valueBytes, buffer);
1304              return true;
1305          }
1306        }
1307    
1308        // If we've gotten here, then the string value is acceptable.
1309        buffer.append(' ');
1310        buffer.append(valueBytes);
1311        return false;
1312      }
1313    
1314    
1315    
1316      /**
1317       * Appends a comment to the provided buffer with an unencoded representation
1318       * of the provided value.  This will only have any effect if
1319       * {@code commentAboutBase64EncodedValues} is {@code true}.
1320       *
1321       * @param  valueBytes  The bytes that comprise the value.
1322       * @param  buffer      The buffer to which the comment should be appended.
1323       * @param  wrapColumn  The column at which to wrap long lines.
1324       */
1325      private static void writeBase64DecodedValueComment(final byte[] valueBytes,
1326                               final ByteStringBuffer buffer,
1327                               final int wrapColumn)
1328      {
1329        if (commentAboutBase64EncodedValues)
1330        {
1331          final int wrapColumnMinusTwo;
1332          if (wrapColumn <= 5)
1333          {
1334            wrapColumnMinusTwo = TERMINAL_WIDTH_COLUMNS - 3;
1335          }
1336          else
1337          {
1338            wrapColumnMinusTwo = wrapColumn - 2;
1339          }
1340    
1341          final int wrapColumnMinusThree = wrapColumnMinusTwo - 1;
1342    
1343          boolean first = true;
1344          final String comment =
1345               "Non-base64-encoded representation of the above value: " +
1346                    getEscapedValue(valueBytes);
1347          for (final String s :
1348               wrapLine(comment, wrapColumnMinusTwo, wrapColumnMinusThree))
1349          {
1350            buffer.append(EOL);
1351            buffer.append("# ");
1352            if (first)
1353            {
1354              first = false;
1355            }
1356            else
1357            {
1358              buffer.append(' ');
1359            }
1360            buffer.append(s);
1361          }
1362        }
1363      }
1364    
1365    
1366    
1367      /**
1368       * Retrieves a string representation of the provided value with all special
1369       * characters escaped with backslashes.
1370       *
1371       * @param  valueBytes  The byte array containing the value to encode.
1372       *
1373       * @return  A string representation of the provided value with any special
1374       *          characters
1375       */
1376      private static String getEscapedValue(final byte[] valueBytes)
1377      {
1378        final StringBuilder buffer = new StringBuilder(valueBytes.length * 2);
1379        for (int i=0; i < valueBytes.length; i++)
1380        {
1381          final byte b = valueBytes[i];
1382          switch (b)
1383          {
1384            case '\n':
1385              buffer.append("\\n");
1386              break;
1387            case '\r':
1388              buffer.append("\\r");
1389              break;
1390            case '\t':
1391              buffer.append("\\t");
1392              break;
1393            case ' ':
1394              if (i == 0)
1395              {
1396                buffer.append("\\ ");
1397              }
1398              else if ( i == (valueBytes.length - 1))
1399              {
1400                buffer.append("\\20");
1401              }
1402              else
1403              {
1404                buffer.append(' ');
1405              }
1406              break;
1407            case '<':
1408              if (i == 0)
1409              {
1410                buffer.append('\\');
1411              }
1412              buffer.append('<');
1413              break;
1414            case ':':
1415              if (i == 0)
1416              {
1417                buffer.append('\\');
1418              }
1419              buffer.append(':');
1420              break;
1421            default:
1422              if ((b >= '!') && (b <= '~'))
1423              {
1424                buffer.append((char) b);
1425              }
1426              else
1427              {
1428                buffer.append("\\");
1429                toHex(b, buffer);
1430              }
1431              break;
1432          }
1433        }
1434    
1435        return buffer.toString();
1436      }
1437    
1438    
1439    
1440      /**
1441       * If the provided exception is non-null, then it will be rethrown as an
1442       * unchecked exception or an IOException.
1443       *
1444       * @param t  The exception to rethrow as an an unchecked exception or an
1445       *           IOException or {@code null} if none.
1446       *
1447       * @throws IOException  If t is a checked exception.
1448       */
1449      static void rethrow(final Throwable t)
1450             throws IOException
1451      {
1452        if (t == null)
1453        {
1454          return;
1455        }
1456    
1457        if (t instanceof IOException)
1458        {
1459          throw (IOException) t;
1460        }
1461        else if (t instanceof RuntimeException)
1462        {
1463          throw (RuntimeException) t;
1464        }
1465        else if (t instanceof Error)
1466        {
1467          throw (Error) t;
1468        }
1469        else
1470        {
1471          throw createIOExceptionWithCause(null, t);
1472        }
1473      }
1474    }