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