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 }