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