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 */
021package com.unboundid.ldif;
022
023
024
025import java.io.File;
026import java.io.IOException;
027import java.io.OutputStream;
028import java.io.FileOutputStream;
029import java.io.BufferedOutputStream;
030import java.util.List;
031import java.util.ArrayList;
032import java.util.Arrays;
033
034import com.unboundid.asn1.ASN1OctetString;
035import com.unboundid.ldap.sdk.Entry;
036import com.unboundid.util.Base64;
037import com.unboundid.util.LDAPSDKThreadFactory;
038import com.unboundid.util.ThreadSafety;
039import com.unboundid.util.ThreadSafetyLevel;
040import com.unboundid.util.ByteStringBuffer;
041import com.unboundid.util.parallel.ParallelProcessor;
042import com.unboundid.util.parallel.Result;
043import com.unboundid.util.parallel.Processor;
044
045import static com.unboundid.util.Debug.*;
046import static com.unboundid.util.StaticUtils.*;
047import 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)
088public 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}