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 */
021package com.unboundid.ldif;
022
023
024
025import java.io.File;
026import java.io.IOException;
027import java.io.OutputStream;
028import java.io.FileOutputStream;
029import java.io.BufferedOutputStream;
030import java.util.List;
031import java.util.ArrayList;
032import java.util.Arrays;
033
034import com.unboundid.asn1.ASN1OctetString;
035import com.unboundid.ldap.sdk.Entry;
036import com.unboundid.util.Base64;
037import com.unboundid.util.LDAPSDKThreadFactory;
038import com.unboundid.util.ThreadSafety;
039import com.unboundid.util.ThreadSafetyLevel;
040import com.unboundid.util.ByteStringBuffer;
041import com.unboundid.util.parallel.ParallelProcessor;
042import com.unboundid.util.parallel.Result;
043import com.unboundid.util.parallel.Processor;
044
045import static com.unboundid.util.Debug.*;
046import static com.unboundid.util.StaticUtils.*;
047import static com.unboundid.util.Validator.*;
048
049
050
051/**
052 * This class provides an LDIF writer, which can be used to write entries and
053 * change records in the LDAP Data Interchange Format as per
054 * <A HREF="http://www.ietf.org/rfc/rfc2849.txt">RFC 2849</A>.
055 * <BR><BR>
056 * <H2>Example</H2>
057 * The following example performs a search to find all users in the "Sales"
058 * department and then writes their entries to an LDIF file:
059 * <PRE>
060 * // Perform a search to find all users who are members of the sales
061 * // department.
062 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com",
063 *      SearchScope.SUB, Filter.createEqualityFilter("ou", "Sales"));
064 * SearchResult searchResult;
065 * try
066 * {
067 *   searchResult = connection.search(searchRequest);
068 * }
069 * catch (LDAPSearchException lse)
070 * {
071 *   searchResult = lse.getSearchResult();
072 * }
073 * LDAPTestUtils.assertResultCodeEquals(searchResult, ResultCode.SUCCESS);
074 *
075 * // Write all of the matching entries to LDIF.
076 * int entriesWritten = 0;
077 * LDIFWriter ldifWriter = new LDIFWriter(pathToLDIF);
078 * for (SearchResultEntry entry : searchResult.getSearchEntries())
079 * {
080 *   ldifWriter.writeEntry(entry);
081 *   entriesWritten++;
082 * }
083 *
084 * ldifWriter.close();
085 * </PRE>
086 */
087@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
088public final class LDIFWriter
089{
090  /**
091   * The bytes that comprise the LDIF version header.
092   */
093  private static final byte[] VERSION_1_HEADER_BYTES =
094       getBytes("version: 1" + EOL);
095
096
097
098  /**
099   * The default buffer size (128KB) that will be used when writing LDIF data
100   * to the appropriate destination.
101   */
102  private static final int DEFAULT_BUFFER_SIZE = 128 * 1024;
103
104
105  // The writer that will be used to actually write the data.
106  private final BufferedOutputStream writer;
107
108  // The byte string buffer that will be used to convert LDIF records to LDIF.
109  // It will only be used when operating synchronously.
110  private final ByteStringBuffer buffer;
111
112  // The translator to use for 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 new IOException(getExceptionMessage(t));
1143    }
1144  }
1145}