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