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