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