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 */ 021package com.unboundid.ldif; 022 023 024 025import java.io.File; 026import java.io.IOException; 027import java.io.OutputStream; 028import java.io.FileOutputStream; 029import java.io.BufferedOutputStream; 030import java.util.List; 031import java.util.ArrayList; 032import java.util.Arrays; 033 034import com.unboundid.asn1.ASN1OctetString; 035import com.unboundid.ldap.sdk.Entry; 036import com.unboundid.util.Base64; 037import com.unboundid.util.LDAPSDKThreadFactory; 038import com.unboundid.util.ThreadSafety; 039import com.unboundid.util.ThreadSafetyLevel; 040import com.unboundid.util.ByteStringBuffer; 041import com.unboundid.util.parallel.ParallelProcessor; 042import com.unboundid.util.parallel.Result; 043import com.unboundid.util.parallel.Processor; 044 045import static com.unboundid.util.Debug.*; 046import static com.unboundid.util.StaticUtils.*; 047import 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) 088public 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}