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