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