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