001 /*
002 * Copyright 2007-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2016 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 change records to be written, if any.
110 private final LDIFWriterChangeRecordTranslator changeRecordTranslator;
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 this(outputStream, parallelThreads, entryTranslator, null);
236 }
237
238
239
240 /**
241 * Creates a new LDIF writer that will write entries to the provided output
242 * stream optionally using parallelThreads when writing batches of LDIF
243 * records.
244 *
245 * @param outputStream The output stream to which the data is to
246 * be written. It must not be {@code null}.
247 * @param parallelThreads If this value is greater than zero, then
248 * the specified number of threads will be
249 * used to encode entries before writing them
250 * to the output for the
251 * {@code writeLDIFRecords(List)} method.
252 * Note this is the only output method that
253 * will use multiple threads. This should
254 * only be set to greater than zero when
255 * performance analysis has demonstrated that
256 * writing the LDIF is a bottleneck. The
257 * default synchronous processing is normally
258 * fast enough. There is no benefit in
259 * passing in a value greater than the number
260 * of processors in the system. A value of
261 * zero implies the default behavior of
262 * reading and parsing LDIF records
263 * synchronously when one of the read methods
264 * is called.
265 * @param entryTranslator An optional translator that will be used to
266 * alter entries before they are actually
267 * written. This may be {@code null} if no
268 * translator is needed.
269 * @param changeRecordTranslator An optional translator that will be used to
270 * alter change records before they are
271 * actually written. This may be {@code null}
272 * if no translator is needed.
273 */
274 public LDIFWriter(final OutputStream outputStream, final int parallelThreads,
275 final LDIFWriterEntryTranslator entryTranslator,
276 final LDIFWriterChangeRecordTranslator changeRecordTranslator)
277 {
278 ensureNotNull(outputStream);
279 ensureTrue(parallelThreads >= 0,
280 "LDIFWriter.parallelThreads must not be negative.");
281
282 this.entryTranslator = entryTranslator;
283 this.changeRecordTranslator = changeRecordTranslator;
284 buffer = new ByteStringBuffer();
285
286 if (outputStream instanceof BufferedOutputStream)
287 {
288 writer = (BufferedOutputStream) outputStream;
289 }
290 else
291 {
292 writer = new BufferedOutputStream(outputStream, DEFAULT_BUFFER_SIZE);
293 }
294
295 if (parallelThreads == 0)
296 {
297 toLdifBytesInvoker = null;
298 }
299 else
300 {
301 final LDAPSDKThreadFactory threadFactory =
302 new LDAPSDKThreadFactory("LDIFWriter Worker", true, null);
303 toLdifBytesInvoker = new ParallelProcessor<LDIFRecord,ByteStringBuffer>(
304 new Processor<LDIFRecord,ByteStringBuffer>() {
305 public ByteStringBuffer process(final LDIFRecord input)
306 throws IOException
307 {
308 final LDIFRecord r;
309 if ((entryTranslator != null) && (input instanceof Entry))
310 {
311 r = entryTranslator.translateEntryToWrite((Entry) input);
312 if (r == null)
313 {
314 return null;
315 }
316 }
317 else if ((changeRecordTranslator != null) &&
318 (input instanceof LDIFChangeRecord))
319 {
320 r = changeRecordTranslator.translateChangeRecordToWrite(
321 (LDIFChangeRecord) input);
322 if (r == null)
323 {
324 return null;
325 }
326 }
327 else
328 {
329 r = input;
330 }
331
332 final ByteStringBuffer b = new ByteStringBuffer(200);
333 r.toLDIF(b, wrapColumn);
334 return b;
335 }
336 }, threadFactory, parallelThreads, 5);
337 }
338 }
339
340
341
342 /**
343 * Flushes the output stream used by this LDIF writer to ensure any buffered
344 * data is written out.
345 *
346 * @throws IOException If a problem occurs while attempting to flush the
347 * output stream.
348 */
349 public void flush()
350 throws IOException
351 {
352 writer.flush();
353 }
354
355
356
357 /**
358 * Closes this LDIF writer and the underlying LDIF target.
359 *
360 * @throws IOException If a problem occurs while closing the underlying LDIF
361 * target.
362 */
363 public void close()
364 throws IOException
365 {
366 try
367 {
368 if (toLdifBytesInvoker != null)
369 {
370 try
371 {
372 toLdifBytesInvoker.shutdown();
373 }
374 catch (InterruptedException e)
375 {
376 debugException(e);
377 }
378 }
379 }
380 finally
381 {
382 writer.close();
383 }
384 }
385
386
387
388 /**
389 * Retrieves the column at which to wrap long lines.
390 *
391 * @return The column at which to wrap long lines, or zero to indicate that
392 * long lines should not be wrapped.
393 */
394 public int getWrapColumn()
395 {
396 return wrapColumn;
397 }
398
399
400
401 /**
402 * Specifies the column at which to wrap long lines. A value of zero
403 * indicates that long lines should not be wrapped.
404 *
405 * @param wrapColumn The column at which to wrap long lines.
406 */
407 public void setWrapColumn(final int wrapColumn)
408 {
409 this.wrapColumn = wrapColumn;
410
411 wrapColumnMinusTwo = wrapColumn - 2;
412 }
413
414
415
416 /**
417 * Writes the LDIF version header (i.e.,"version: 1"). If a version header
418 * is to be added to the LDIF content, it should be done before any entries or
419 * change records have been written.
420 *
421 * @throws IOException If a problem occurs while writing the version header.
422 */
423 public void writeVersionHeader()
424 throws IOException
425 {
426 writer.write(VERSION_1_HEADER_BYTES);
427 }
428
429
430
431 /**
432 * Writes the provided entry in LDIF form.
433 *
434 * @param entry The entry to be written. It must not be {@code null}.
435 *
436 * @throws IOException If a problem occurs while writing the LDIF data.
437 */
438 public void writeEntry(final Entry entry)
439 throws IOException
440 {
441 writeEntry(entry, null);
442 }
443
444
445
446 /**
447 * Writes the provided entry in LDIF form, preceded by the provided comment.
448 *
449 * @param entry The entry to be written in LDIF form. It must not be
450 * {@code null}.
451 * @param comment The comment to be written before the entry. It may be
452 * {@code null} if no comment is to be written.
453 *
454 * @throws IOException If a problem occurs while writing the LDIF data.
455 */
456 public void writeEntry(final Entry entry, final String comment)
457 throws IOException
458 {
459 ensureNotNull(entry);
460
461 final Entry e;
462 if (entryTranslator == null)
463 {
464 e = entry;
465 }
466 else
467 {
468 e = entryTranslator.translateEntryToWrite(entry);
469 if (e == null)
470 {
471 return;
472 }
473 }
474
475 if (comment != null)
476 {
477 writeComment(comment, false, false);
478 }
479
480 debugLDIFWrite(e);
481 writeLDIF(e);
482 }
483
484
485
486 /**
487 * Writes the provided change record in LDIF form.
488 *
489 * @param changeRecord The change record to be written. It must not be
490 * {@code null}.
491 *
492 * @throws IOException If a problem occurs while writing the LDIF data.
493 */
494 public void writeChangeRecord(final LDIFChangeRecord changeRecord)
495 throws IOException
496 {
497 writeChangeRecord(changeRecord, null);
498 }
499
500
501
502 /**
503 * Writes the provided change record in LDIF form, preceded by the provided
504 * comment.
505 *
506 * @param changeRecord The change record to be written. It must not be
507 * {@code null}.
508 * @param comment The comment to be written before the entry. It may
509 * be {@code null} if no comment is to be written.
510 *
511 * @throws IOException If a problem occurs while writing the LDIF data.
512 */
513 public void writeChangeRecord(final LDIFChangeRecord changeRecord,
514 final String comment)
515 throws IOException
516 {
517 ensureNotNull(changeRecord);
518
519 final LDIFChangeRecord r;
520 if (changeRecordTranslator == null)
521 {
522 r = changeRecord;
523 }
524 else
525 {
526 r = changeRecordTranslator.translateChangeRecordToWrite(changeRecord);
527 if (r == null)
528 {
529 return;
530 }
531 }
532
533 if (comment != null)
534 {
535 writeComment(comment, false, false);
536 }
537
538 debugLDIFWrite(r);
539 writeLDIF(r);
540 }
541
542
543
544 /**
545 * Writes the provided record in LDIF form.
546 *
547 * @param record The LDIF record to be written. It must not be
548 * {@code null}.
549 *
550 * @throws IOException If a problem occurs while writing the LDIF data.
551 */
552 public void writeLDIFRecord(final LDIFRecord record)
553 throws IOException
554 {
555 writeLDIFRecord(record, null);
556 }
557
558
559
560 /**
561 * Writes the provided record in LDIF form, preceded by the provided comment.
562 *
563 * @param record The LDIF record to be written. It must not be
564 * {@code null}.
565 * @param comment The comment to be written before the LDIF record. It may
566 * be {@code null} if no comment is to be written.
567 *
568 * @throws IOException If a problem occurs while writing the LDIF data.
569 */
570 public void writeLDIFRecord(final LDIFRecord record, final String comment)
571 throws IOException
572 {
573 ensureNotNull(record);
574
575 final LDIFRecord r;
576 if ((entryTranslator != null) && (record instanceof Entry))
577 {
578 r = entryTranslator.translateEntryToWrite((Entry) record);
579 if (r == null)
580 {
581 return;
582 }
583 }
584 else if ((changeRecordTranslator != null) &&
585 (record instanceof LDIFChangeRecord))
586 {
587 r = changeRecordTranslator.translateChangeRecordToWrite(
588 (LDIFChangeRecord) record);
589 if (r == null)
590 {
591 return;
592 }
593 }
594 else
595 {
596 r = record;
597 }
598
599 debugLDIFWrite(r);
600 if (comment != null)
601 {
602 writeComment(comment, false, false);
603 }
604
605 writeLDIF(r);
606 }
607
608
609
610 /**
611 * Writes the provided list of LDIF records (most likely Entries) to the
612 * output. If this LDIFWriter was constructed without any parallel
613 * output threads, then this behaves identically to calling
614 * {@code writeLDIFRecord()} sequentially for each item in the list.
615 * If this LDIFWriter was constructed to write records in parallel, then
616 * the configured number of threads are used to convert the records to raw
617 * bytes, which are sequentially written to the input file. This can speed up
618 * the total time to write a large set of records. Either way, the output
619 * records are guaranteed to be written in the order they appear in the list.
620 *
621 * @param ldifRecords The LDIF records (most likely entries) to write to the
622 * output.
623 *
624 * @throws IOException If a problem occurs while writing the LDIF data.
625 *
626 * @throws InterruptedException If this thread is interrupted while waiting
627 * for the records to be written to the output.
628 */
629 public void writeLDIFRecords(final List<? extends LDIFRecord> ldifRecords)
630 throws IOException, InterruptedException
631 {
632 if (toLdifBytesInvoker == null)
633 {
634 for (final LDIFRecord ldifRecord : ldifRecords)
635 {
636 writeLDIFRecord(ldifRecord);
637 }
638 }
639 else
640 {
641 final List<Result<LDIFRecord,ByteStringBuffer>> results =
642 toLdifBytesInvoker.processAll(ldifRecords);
643 for (final Result<LDIFRecord,ByteStringBuffer> result: results)
644 {
645 rethrow(result.getFailureCause());
646
647 final ByteStringBuffer encodedBytes = result.getOutput();
648 if (encodedBytes != null)
649 {
650 encodedBytes.write(writer);
651 writer.write(EOL_BYTES);
652 }
653 }
654 }
655 }
656
657
658
659
660 /**
661 * Writes the provided comment to the LDIF target, wrapping long lines as
662 * necessary.
663 *
664 * @param comment The comment to be written to the LDIF target. It must
665 * not be {@code null}.
666 * @param spaceBefore Indicates whether to insert a blank line before the
667 * comment.
668 * @param spaceAfter Indicates whether to insert a blank line after the
669 * comment.
670 *
671 * @throws IOException If a problem occurs while writing the LDIF data.
672 */
673 public void writeComment(final String comment, final boolean spaceBefore,
674 final boolean spaceAfter)
675 throws IOException
676 {
677 ensureNotNull(comment);
678 if (spaceBefore)
679 {
680 writer.write(EOL_BYTES);
681 }
682
683 //
684 // Check for a newline explicitly to avoid the overhead of the regex
685 // for the common case of a single-line comment.
686 //
687
688 if (comment.indexOf('\n') < 0)
689 {
690 writeSingleLineComment(comment);
691 }
692 else
693 {
694 //
695 // Split on blank lines and wrap each line individually.
696 //
697
698 final String[] lines = comment.split("\\r?\\n");
699 for (final String line: lines)
700 {
701 writeSingleLineComment(line);
702 }
703 }
704
705 if (spaceAfter)
706 {
707 writer.write(EOL_BYTES);
708 }
709 }
710
711
712
713 /**
714 * Writes the provided comment to the LDIF target, wrapping long lines as
715 * necessary.
716 *
717 * @param comment The comment to be written to the LDIF target. It must
718 * not be {@code null}, and it must not include any line
719 * breaks.
720 *
721 * @throws IOException If a problem occurs while writing the LDIF data.
722 */
723 private void writeSingleLineComment(final String comment)
724 throws IOException
725 {
726 // We will always wrap comments, even if we won't wrap LDIF entries. If
727 // there is a wrap column set, then use it. Otherwise use 79 characters,
728 // and back off two characters for the "# " at the beginning.
729 final int commentWrapMinusTwo;
730 if (wrapColumn <= 0)
731 {
732 commentWrapMinusTwo = 77;
733 }
734 else
735 {
736 commentWrapMinusTwo = wrapColumnMinusTwo;
737 }
738
739 buffer.clear();
740 final int length = comment.length();
741 if (length <= commentWrapMinusTwo)
742 {
743 buffer.append("# ");
744 buffer.append(comment);
745 buffer.append(EOL_BYTES);
746 }
747 else
748 {
749 int minPos = 0;
750 while (minPos < length)
751 {
752 if ((length - minPos) <= commentWrapMinusTwo)
753 {
754 buffer.append("# ");
755 buffer.append(comment.substring(minPos));
756 buffer.append(EOL_BYTES);
757 break;
758 }
759
760 // First, adjust the position until we find a space. Go backwards if
761 // possible, but if we can't find one there then go forward.
762 boolean spaceFound = false;
763 final int pos = minPos + commentWrapMinusTwo;
764 int spacePos = pos;
765 while (spacePos > minPos)
766 {
767 if (comment.charAt(spacePos) == ' ')
768 {
769 spaceFound = true;
770 break;
771 }
772
773 spacePos--;
774 }
775
776 if (! spaceFound)
777 {
778 spacePos = pos + 1;
779 while (spacePos < length)
780 {
781 if (comment.charAt(spacePos) == ' ')
782 {
783 spaceFound = true;
784 break;
785 }
786
787 spacePos++;
788 }
789
790 if (! spaceFound)
791 {
792 // There are no spaces at all in the remainder of the comment, so
793 // we'll just write the remainder of it all at once.
794 buffer.append("# ");
795 buffer.append(comment.substring(minPos));
796 buffer.append(EOL_BYTES);
797 break;
798 }
799 }
800
801 // We have a space, so we'll write up to the space position and then
802 // start up after the next space.
803 buffer.append("# ");
804 buffer.append(comment.substring(minPos, spacePos));
805 buffer.append(EOL_BYTES);
806
807 minPos = spacePos + 1;
808 while ((minPos < length) && (comment.charAt(minPos) == ' '))
809 {
810 minPos++;
811 }
812 }
813 }
814
815 buffer.write(writer);
816 }
817
818
819
820 /**
821 * Writes the provided record to the LDIF target, wrapping long lines as
822 * necessary.
823 *
824 * @param record The LDIF record to be written.
825 *
826 * @throws IOException If a problem occurs while writing the LDIF data.
827 */
828 private void writeLDIF(final LDIFRecord record)
829 throws IOException
830 {
831 buffer.clear();
832 record.toLDIF(buffer, wrapColumn);
833 buffer.append(EOL_BYTES);
834 buffer.write(writer);
835 }
836
837
838
839 /**
840 * Performs any appropriate wrapping for the provided set of LDIF lines.
841 *
842 * @param wrapColumn The column at which to wrap long lines. A value that
843 * is less than or equal to two indicates that no
844 * wrapping should be performed.
845 * @param ldifLines The set of lines that make up the LDIF data to be
846 * wrapped.
847 *
848 * @return A new list of lines that have been wrapped as appropriate.
849 */
850 public static List<String> wrapLines(final int wrapColumn,
851 final String... ldifLines)
852 {
853 return wrapLines(wrapColumn, Arrays.asList(ldifLines));
854 }
855
856
857
858 /**
859 * Performs any appropriate wrapping for the provided set of LDIF lines.
860 *
861 * @param wrapColumn The column at which to wrap long lines. A value that
862 * is less than or equal to two indicates that no
863 * wrapping should be performed.
864 * @param ldifLines The set of lines that make up the LDIF data to be
865 * wrapped.
866 *
867 * @return A new list of lines that have been wrapped as appropriate.
868 */
869 public static List<String> wrapLines(final int wrapColumn,
870 final List<String> ldifLines)
871 {
872 if (wrapColumn <= 2)
873 {
874 return new ArrayList<String>(ldifLines);
875 }
876
877 final ArrayList<String> newLines = new ArrayList<String>(ldifLines.size());
878 for (final String s : ldifLines)
879 {
880 final int length = s.length();
881 if (length <= wrapColumn)
882 {
883 newLines.add(s);
884 continue;
885 }
886
887 newLines.add(s.substring(0, wrapColumn));
888
889 int pos = wrapColumn;
890 while (pos < length)
891 {
892 if ((length - pos + 1) <= wrapColumn)
893 {
894 newLines.add(' ' + s.substring(pos));
895 break;
896 }
897 else
898 {
899 newLines.add(' ' + s.substring(pos, (pos+wrapColumn-1)));
900 pos += wrapColumn - 1;
901 }
902 }
903 }
904
905 return newLines;
906 }
907
908
909
910 /**
911 * Creates a string consisting of the provided attribute name followed by
912 * either a single colon and the string representation of the provided value,
913 * or two colons and the base64-encoded representation of the provided value.
914 *
915 * @param name The name for the attribute.
916 * @param value The value for the attribute.
917 *
918 * @return A string consisting of the provided attribute name followed by
919 * either a single colon and the string representation of the
920 * provided value, or two colons and the base64-encoded
921 * representation of the provided value.
922 */
923 public static String encodeNameAndValue(final String name,
924 final ASN1OctetString value)
925 {
926 final StringBuilder buffer = new StringBuilder();
927 encodeNameAndValue(name, value, buffer);
928 return buffer.toString();
929 }
930
931
932
933 /**
934 * Appends a string to the provided buffer consisting of the provided
935 * attribute name followed by either a single colon and the string
936 * representation of the provided value, or two colons and the base64-encoded
937 * representation of the provided value.
938 *
939 * @param name The name for the attribute.
940 * @param value The value for the attribute.
941 * @param buffer The buffer to which the name and value are to be written.
942 */
943 public static void encodeNameAndValue(final String name,
944 final ASN1OctetString value,
945 final StringBuilder buffer)
946 {
947 encodeNameAndValue(name, value, buffer, 0);
948 }
949
950
951
952 /**
953 * Appends a string to the provided buffer consisting of the provided
954 * attribute name followed by either a single colon and the string
955 * representation of the provided value, or two colons and the base64-encoded
956 * representation of the provided value.
957 *
958 * @param name The name for the attribute.
959 * @param value The value for the attribute.
960 * @param buffer The buffer to which the name and value are to be
961 * written.
962 * @param wrapColumn The column at which to wrap long lines. A value that
963 * is less than or equal to two indicates that no
964 * wrapping should be performed.
965 */
966 public static void encodeNameAndValue(final String name,
967 final ASN1OctetString value,
968 final StringBuilder buffer,
969 final int wrapColumn)
970 {
971 final int bufferStartPos = buffer.length();
972
973 try
974 {
975 buffer.append(name);
976 buffer.append(':');
977
978 final byte[] valueBytes = value.getValue();
979 final int length = valueBytes.length;
980 if (length == 0)
981 {
982 buffer.append(' ');
983 return;
984 }
985
986 // If the value starts with a space, colon, or less-than character, then
987 // it must be base64-encoded.
988 switch (valueBytes[0])
989 {
990 case ' ':
991 case ':':
992 case '<':
993 buffer.append(": ");
994 Base64.encode(valueBytes, buffer);
995 return;
996 }
997
998 // If the value ends with a space, then it should be base64-encoded.
999 if (valueBytes[length-1] == ' ')
1000 {
1001 buffer.append(": ");
1002 Base64.encode(valueBytes, buffer);
1003 return;
1004 }
1005
1006 // If any character in the value is outside the ASCII range, or is the
1007 // NUL, LF, or CR character, then the value should be base64-encoded.
1008 for (int i=0; i < length; i++)
1009 {
1010 if ((valueBytes[i] & 0x7F) != (valueBytes[i] & 0xFF))
1011 {
1012 buffer.append(": ");
1013 Base64.encode(valueBytes, buffer);
1014 return;
1015 }
1016
1017 switch (valueBytes[i])
1018 {
1019 case 0x00: // The NUL character
1020 case 0x0A: // The LF character
1021 case 0x0D: // The CR character
1022 buffer.append(": ");
1023 Base64.encode(valueBytes, buffer);
1024 return;
1025 }
1026 }
1027
1028 // If we've gotten here, then the string value is acceptable.
1029 buffer.append(' ');
1030 buffer.append(value.stringValue());
1031 }
1032 finally
1033 {
1034 if (wrapColumn > 2)
1035 {
1036 final int length = buffer.length() - bufferStartPos;
1037 if (length > wrapColumn)
1038 {
1039 final String EOL_PLUS_SPACE = EOL + ' ';
1040 buffer.insert((bufferStartPos+wrapColumn), EOL_PLUS_SPACE);
1041
1042 int pos = bufferStartPos + (2*wrapColumn) +
1043 EOL_PLUS_SPACE.length() - 1;
1044 while (pos < buffer.length())
1045 {
1046 buffer.insert(pos, EOL_PLUS_SPACE);
1047 pos += (wrapColumn - 1 + EOL_PLUS_SPACE.length());
1048 }
1049 }
1050 }
1051 }
1052 }
1053
1054
1055
1056 /**
1057 * Appends a string to the provided buffer consisting of the provided
1058 * attribute name followed by either a single colon and the string
1059 * representation of the provided value, or two colons and the base64-encoded
1060 * representation of the provided value. It may optionally be wrapped at the
1061 * specified column.
1062 *
1063 * @param name The name for the attribute.
1064 * @param value The value for the attribute.
1065 * @param buffer The buffer to which the name and value are to be
1066 * written.
1067 * @param wrapColumn The column at which to wrap long lines. A value that
1068 * is less than or equal to two indicates that no
1069 * wrapping should be performed.
1070 */
1071 public static void encodeNameAndValue(final String name,
1072 final ASN1OctetString value,
1073 final ByteStringBuffer buffer,
1074 final int wrapColumn)
1075 {
1076 final int bufferStartPos = buffer.length();
1077
1078 try
1079 {
1080 buffer.append(name);
1081 encodeValue(value, buffer);
1082 }
1083 finally
1084 {
1085 if (wrapColumn > 2)
1086 {
1087 final int length = buffer.length() - bufferStartPos;
1088 if (length > wrapColumn)
1089 {
1090 final byte[] EOL_BYTES_PLUS_SPACE = new byte[EOL_BYTES.length + 1];
1091 System.arraycopy(EOL_BYTES, 0, EOL_BYTES_PLUS_SPACE, 0,
1092 EOL_BYTES.length);
1093 EOL_BYTES_PLUS_SPACE[EOL_BYTES.length] = ' ';
1094
1095 buffer.insert((bufferStartPos+wrapColumn), EOL_BYTES_PLUS_SPACE);
1096
1097 int pos = bufferStartPos + (2*wrapColumn) +
1098 EOL_BYTES_PLUS_SPACE.length - 1;
1099 while (pos < buffer.length())
1100 {
1101 buffer.insert(pos, EOL_BYTES_PLUS_SPACE);
1102 pos += (wrapColumn - 1 + EOL_BYTES_PLUS_SPACE.length);
1103 }
1104 }
1105 }
1106 }
1107 }
1108
1109
1110
1111 /**
1112 * Appends a string to the provided buffer consisting of the properly-encoded
1113 * representation of the provided value, including the necessary colon(s) and
1114 * space that precede it. Depending on the content of the value, it will
1115 * either be used as-is or base64-encoded.
1116 *
1117 * @param value The value for the attribute.
1118 * @param buffer The buffer to which the value is to be written.
1119 */
1120 static void encodeValue(final ASN1OctetString value,
1121 final ByteStringBuffer buffer)
1122 {
1123 buffer.append(':');
1124
1125 final byte[] valueBytes = value.getValue();
1126 final int length = valueBytes.length;
1127 if (length == 0)
1128 {
1129 buffer.append(' ');
1130 return;
1131 }
1132
1133 // If the value starts with a space, colon, or less-than character, then
1134 // it must be base64-encoded.
1135 switch (valueBytes[0])
1136 {
1137 case ' ':
1138 case ':':
1139 case '<':
1140 buffer.append(':');
1141 buffer.append(' ');
1142 Base64.encode(valueBytes, buffer);
1143 return;
1144 }
1145
1146 // If the value ends with a space, then it should be base64-encoded.
1147 if (valueBytes[length-1] == ' ')
1148 {
1149 buffer.append(':');
1150 buffer.append(' ');
1151 Base64.encode(valueBytes, buffer);
1152 return;
1153 }
1154
1155 // If any character in the value is outside the ASCII range, or is the
1156 // NUL, LF, or CR character, then the value should be base64-encoded.
1157 for (int i=0; i < length; i++)
1158 {
1159 if ((valueBytes[i] & 0x7F) != (valueBytes[i] & 0xFF))
1160 {
1161 buffer.append(':');
1162 buffer.append(' ');
1163 Base64.encode(valueBytes, buffer);
1164 return;
1165 }
1166
1167 switch (valueBytes[i])
1168 {
1169 case 0x00: // The NUL character
1170 case 0x0A: // The LF character
1171 case 0x0D: // The CR character
1172 buffer.append(':');
1173 buffer.append(' ');
1174 Base64.encode(valueBytes, buffer);
1175 return;
1176 }
1177 }
1178
1179 // If we've gotten here, then the string value is acceptable.
1180 buffer.append(' ');
1181 buffer.append(valueBytes);
1182 }
1183
1184
1185
1186 /**
1187 * If the provided exception is non-null, then it will be rethrown as an
1188 * unchecked exception or an IOException.
1189 *
1190 * @param t The exception to rethrow as an an unchecked exception or an
1191 * IOException or {@code null} if none.
1192 *
1193 * @throws IOException If t is a checked exception.
1194 */
1195 static void rethrow(final Throwable t)
1196 throws IOException
1197 {
1198 if (t == null)
1199 {
1200 return;
1201 }
1202
1203 if (t instanceof IOException)
1204 {
1205 throw (IOException) t;
1206 }
1207 else if (t instanceof RuntimeException)
1208 {
1209 throw (RuntimeException) t;
1210 }
1211 else if (t instanceof Error)
1212 {
1213 throw (Error) t;
1214 }
1215 else
1216 {
1217 throw createIOExceptionWithCause(null, t);
1218 }
1219 }
1220 }