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