001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2016 ForgeRock AS.
016 */
017package org.opends.server.util;
018
019import java.io.BufferedWriter;
020import java.io.Closeable;
021import java.io.IOException;
022import java.util.Collection;
023import java.util.Iterator;
024import java.util.List;
025import java.util.regex.Pattern;
026
027import org.forgerock.i18n.LocalizableMessage;
028import org.forgerock.opendj.ldap.ByteSequence;
029import org.forgerock.opendj.ldap.ByteString;
030import org.forgerock.opendj.ldap.DN;
031import org.forgerock.opendj.ldap.RDN;
032import org.forgerock.opendj.ldap.schema.AttributeType;
033import org.opends.server.tools.makeldif.TemplateEntry;
034import org.opends.server.types.Attribute;
035import org.opends.server.types.Entry;
036import org.opends.server.types.LDIFExportConfig;
037import org.opends.server.types.Modification;
038import org.opends.server.types.RawAttribute;
039import org.opends.server.types.RawModification;
040
041import static org.forgerock.util.Reject.*;
042import static org.opends.server.util.StaticUtils.*;
043
044/**
045 * This class provides a mechanism for writing entries in LDIF form to a file or
046 * an output stream.
047 */
048@org.opends.server.types.PublicAPI(
049     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
050     mayInstantiate=true,
051     mayExtend=false,
052     mayInvoke=true)
053public final class LDIFWriter implements Closeable
054{
055  // FIXME -- Add support for generating a hash when writing the data.
056  // FIXME -- Add support for signing the hash that is generated.
057
058  /** The writer to which the LDIF information will be written. */
059  private BufferedWriter writer;
060
061  /** The configuration to use for the export. */
062  private LDIFExportConfig exportConfig;
063
064  /** Regular expression used for splitting comments on line-breaks. */
065  private static final Pattern SPLIT_NEWLINE = Pattern.compile("\\r?\\n");
066
067
068
069  /**
070   * Creates a new LDIF writer with the provided configuration.
071   *
072   * @param  exportConfig  The configuration to use for the export.  It must not
073   *                       be <CODE>null</CODE>.
074   *
075   * @throws  IOException  If a problem occurs while opening the writer.
076   */
077  public LDIFWriter(LDIFExportConfig exportConfig)
078         throws IOException
079  {
080    ifNull(exportConfig);
081    this.exportConfig = exportConfig;
082
083    writer = exportConfig.getWriter();
084  }
085
086
087
088  /**
089   * Writes the provided comment to the LDIF file, optionally wrapping near the
090   * specified column.  Each line will be prefixed by the octothorpe (#)
091   * character followed by a space.  If the comment should be wrapped at a
092   * specified column, then it will attempt to do so at the first whitespace
093   * character at or before that column (so it will try not wrap in the middle
094   * of a word).
095   * <BR><BR>
096   * This comment will be ignored by the
097   * Directory Server's LDIF reader, as well as any other compliant LDIF parsing
098   * software.
099   *
100   * @param  comment     The comment to be written.  Any line breaks that it
101   *                     contains will be honored, and potentially new line
102   *                     breaks may be introduced by the wrapping process.  It
103   *                     must not be <CODE>null</CODE>.
104   * @param  wrapColumn  The column at which long lines should be wrapped, or
105   *                     -1 to indicate that no additional wrapping should be
106   *                     added.  This will override the wrap column setting
107   *                     specified in the LDIF export configuration.
108   *
109   * @throws  IOException  If a problem occurs while attempting to write the
110   *                       comment to the LDIF file.
111   */
112  public void writeComment(LocalizableMessage comment, int wrapColumn)
113         throws IOException
114  {
115    ifNull(comment);
116
117
118    // First, break up the comment into multiple lines to preserve the original
119    // spacing that it contained.
120    String[] lines = SPLIT_NEWLINE.split(comment);
121
122    // Now iterate through the lines and write them out, prefixing and wrapping
123    // them as necessary.
124    for (String l : lines)
125    {
126      if (wrapColumn <= 0)
127      {
128        writer.write("# ");
129        writer.write(l);
130        writer.newLine();
131      }
132      else
133      {
134        int breakColumn = wrapColumn - 2;
135
136        if (l.length() <= breakColumn)
137        {
138          writer.write("# ");
139          writer.write(l);
140          writer.newLine();
141        }
142        else
143        {
144          int startPos = 0;
145outerLoop:
146          while (startPos < l.length())
147          {
148            if (startPos + breakColumn >= l.length())
149            {
150              writer.write("# ");
151              writer.write(l.substring(startPos));
152              writer.newLine();
153              startPos = l.length();
154            }
155            else
156            {
157              int endPos = startPos + breakColumn;
158
159              int i=endPos - 1;
160              while (i > startPos)
161              {
162                if (l.charAt(i) == ' ')
163                {
164                  writer.write("# ");
165                  writer.write(l.substring(startPos, i));
166                  writer.newLine();
167
168                  startPos = i+1;
169                  continue outerLoop;
170                }
171
172                i--;
173              }
174
175              // If we've gotten here, then there are no spaces on the entire
176              // line.  If that happens, then we'll have to break in the middle
177              // of a word.
178              writer.write("# ");
179              writer.write(l.substring(startPos, endPos));
180              writer.newLine();
181
182              startPos = endPos;
183            }
184          }
185        }
186      }
187    }
188  }
189
190  /**
191 * Iterates over each entry contained in the map and writes out the entry in
192 * LDIF format. The main benefit of this method is that the entries can be
193 * sorted by DN and output in sorted order.
194 *
195 * @param entries The Map containing the entries keyed by DN.
196 *
197 * @return <CODE>true</CODE>of all of the entries were
198 *                  written out, <CODE>false</CODE>if it was not
199 *                  because of the export configuration.
200 *
201 * @throws IOException  If a problem occurs while writing the entry to LDIF.
202 *
203 * @throws LDIFException If a problem occurs while trying to determine
204 *                         whether to include the entry in the export.
205 */
206  public boolean writeEntries(Collection<Entry> entries) throws IOException,
207      LDIFException
208  {
209    for (Entry entry : entries)
210    {
211      if (!writeEntry(entry))
212      {
213        return false;
214      }
215    }
216    return true;
217  }
218
219
220  /**
221   * Writes the provided entry to LDIF.
222   *
223   * @param  entry  The entry to be written.  It must not be <CODE>null</CODE>.
224   *
225   * @return  <CODE>true</CODE> if the entry was actually written, or
226   *          <CODE>false</CODE> if it was not because of the export
227   *          configuration.
228   *
229   * @throws  IOException  If a problem occurs while writing the entry to LDIF.
230   *
231   * @throws  LDIFException  If a problem occurs while trying to determine
232   *                         whether to include the entry in the export.
233   */
234  public boolean writeEntry(Entry entry)
235         throws IOException, LDIFException
236  {
237    ifNull(entry);
238    return entry.toLDIF(exportConfig);
239  }
240
241
242  /**
243   * Writes the provided template entry to LDIF.
244   *
245   * @param  templateEntry  The template entry to be written.  It must not be
246   * <CODE>null</CODE>.
247   *
248   * @return  <CODE>true</CODE> if the entry was actually written, or
249   *          <CODE>false</CODE> if it was not because of the export
250   *          configuration.
251   *
252   * @throws  IOException  If a problem occurs while writing the template entry
253   *                       to LDIF.
254   *
255   * @throws  LDIFException  If a problem occurs while trying to determine
256   *                         whether to include the template entry in the
257   *                         export.
258   */
259  public boolean writeTemplateEntry(TemplateEntry templateEntry)
260  throws IOException, LDIFException
261  {
262    ifNull(templateEntry);
263    return templateEntry.toLDIF(exportConfig);
264  }
265
266  /**
267   * Writes a change record entry for the provided change record.
268   *
269   * @param  changeRecord  The change record entry to be written.
270   *
271   * @throws  IOException  If a problem occurs while writing the change record.
272   */
273  public void writeChangeRecord(ChangeRecordEntry changeRecord)
274         throws IOException
275  {
276    ifNull(changeRecord);
277
278
279    // Get the information necessary to write the LDIF.
280    BufferedWriter writer     = exportConfig.getWriter();
281    int            wrapColumn = exportConfig.getWrapColumn();
282    boolean        wrapLines  = wrapColumn > 1;
283
284
285    // First, write the DN.
286    writeDN("dn", changeRecord.getDN(), writer, wrapLines, wrapColumn);
287
288
289    // Figure out what type of change it is and act accordingly.
290    if (changeRecord instanceof AddChangeRecordEntry)
291    {
292      StringBuilder changeTypeLine = new StringBuilder("changetype: add");
293      writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn);
294
295      AddChangeRecordEntry addRecord = (AddChangeRecordEntry) changeRecord;
296      for (Attribute a : addRecord.getAttributes())
297      {
298        for (ByteString v : a)
299        {
300          final String attrName = a.getAttributeDescription().toString();
301          writeAttribute(attrName, v, writer, wrapLines, wrapColumn);
302        }
303      }
304    }
305    else if (changeRecord instanceof DeleteChangeRecordEntry)
306    {
307      StringBuilder changeTypeLine = new StringBuilder("changetype: delete");
308      writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn);
309    }
310    else if (changeRecord instanceof ModifyChangeRecordEntry)
311    {
312      StringBuilder changeTypeLine = new StringBuilder("changetype: modify");
313      writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn);
314
315      ModifyChangeRecordEntry modifyRecord =
316           (ModifyChangeRecordEntry) changeRecord;
317      List<RawModification> mods = modifyRecord.getModifications();
318      Iterator<RawModification> iterator = mods.iterator();
319      while (iterator.hasNext())
320      {
321        RawModification m = iterator.next();
322        RawAttribute a = m.getAttribute();
323        String attrName = a.getAttributeType();
324        StringBuilder modTypeLine = new StringBuilder();
325        modTypeLine.append(m.getModificationType());
326        modTypeLine.append(": ");
327        modTypeLine.append(attrName);
328        writeLDIFLine(modTypeLine, writer, wrapLines, wrapColumn);
329
330        for (ByteString s : a.getValues())
331        {
332          StringBuilder valueLine = new StringBuilder(attrName);
333          String stringValue = s.toString();
334
335          if (needsBase64Encoding(stringValue))
336          {
337            valueLine.append(":: ");
338            valueLine.append(Base64.encode(s));
339          }
340          else
341          {
342            valueLine.append(": ");
343            valueLine.append(stringValue);
344          }
345
346          writeLDIFLine(valueLine, writer, wrapLines, wrapColumn);
347        }
348
349        if (iterator.hasNext())
350        {
351          StringBuilder dashLine = new StringBuilder("-");
352          writeLDIFLine(dashLine, writer, wrapLines, wrapColumn);
353        }
354      }
355    }
356    else if (changeRecord instanceof ModifyDNChangeRecordEntry)
357    {
358      StringBuilder changeTypeLine = new StringBuilder("changetype: moddn");
359      writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn);
360
361      ModifyDNChangeRecordEntry modifyDNRecord =
362           (ModifyDNChangeRecordEntry) changeRecord;
363
364      StringBuilder newRDNLine = new StringBuilder("newrdn: ");
365      newRDNLine.append(modifyDNRecord.getNewRDN());
366      writeLDIFLine(newRDNLine, writer, wrapLines, wrapColumn);
367
368      StringBuilder deleteOldRDNLine = new StringBuilder("deleteoldrdn: ");
369      deleteOldRDNLine.append(modifyDNRecord.deleteOldRDN() ? "1" : "0");
370      writeLDIFLine(deleteOldRDNLine, writer, wrapLines, wrapColumn);
371
372      DN newSuperiorDN = modifyDNRecord.getNewSuperiorDN();
373      if (newSuperiorDN != null)
374      {
375        StringBuilder newSuperiorLine = new StringBuilder("newsuperior: ");
376        newSuperiorLine.append(newSuperiorDN);
377        writeLDIFLine(newSuperiorLine, writer, wrapLines, wrapColumn);
378      }
379    }
380
381
382    // Make sure there is a blank line after the entry.
383    writer.newLine();
384  }
385
386
387
388  /**
389   * Writes an add change record for the provided entry.  No filtering will be
390   * performed for this entry, nor will any export plugins be invoked.  Further,
391   * only the user attributes will be included.
392   *
393   * @param  entry  The entry to include in the add change record.  It must not
394   *                be <CODE>null</CODE>.
395   *
396   * @throws  IOException  If a problem occurs while writing the add record.
397   */
398  public void writeAddChangeRecord(Entry entry)
399         throws IOException
400  {
401    ifNull(entry);
402
403
404    // Get the information necessary to write the LDIF.
405    BufferedWriter writer     = exportConfig.getWriter();
406    int            wrapColumn = exportConfig.getWrapColumn();
407    boolean        wrapLines  = wrapColumn > 1;
408
409
410    // First, write the DN.
411    writeDN("dn", entry.getName(), writer, wrapLines, wrapColumn);
412
413
414    // Next, the changetype.
415    StringBuilder changeTypeLine = new StringBuilder("changetype: add");
416    writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn);
417
418
419    // Now the objectclasses.
420    for (String s : entry.getObjectClasses().values())
421    {
422      StringBuilder ocLine = new StringBuilder();
423      ocLine.append("objectClass: ");
424      ocLine.append(s);
425      writeLDIFLine(ocLine, writer, wrapLines, wrapColumn);
426    }
427
428
429    // Finally, the set of user attributes.
430    for (AttributeType attrType : entry.getUserAttributes().keySet())
431    {
432      for (Attribute a : entry.getUserAttribute(attrType))
433      {
434        String attrName = a.getAttributeDescription().toString();
435        for (ByteString v : a)
436        {
437          writeAttribute(attrName, v, writer, wrapLines, wrapColumn);
438        }
439      }
440    }
441
442
443    // Make sure there is a blank line after the entry.
444    writer.newLine();
445  }
446
447
448
449  /**
450   * Writes a delete change record for the provided entry, optionally including
451   * a comment with the full entry contents.  No filtering will be performed for
452   * this entry, nor will any export plugins be invoked.  Further, only the user
453   * attributes will be included.
454   *
455   * @param  entry         The entry to include in the delete change record.  It
456   *                       must not be <CODE>null</CODE>.
457   * @param  commentEntry  Indicates whether to include a comment with the
458   *                       contents of the entry.
459   *
460   * @throws  IOException  If a problem occurs while writing the delete record.
461   */
462  public void writeDeleteChangeRecord(Entry entry, boolean commentEntry)
463         throws IOException
464  {
465    ifNull(entry);
466
467    // Get the information necessary to write the LDIF.
468    BufferedWriter writer     = exportConfig.getWriter();
469    int            wrapColumn = exportConfig.getWrapColumn();
470    boolean        wrapLines  = wrapColumn > 1;
471
472
473    // Add the DN and changetype lines.
474    writeDN("dn", entry.getName(), writer, wrapLines, wrapColumn);
475
476    StringBuilder changeTypeLine = new StringBuilder("changetype: delete");
477    writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn);
478
479
480    // If we should include a comment with the rest of the entry contents, then
481    // do so now.
482    if (commentEntry)
483    {
484      // Write the objectclasses.
485      for (String s : entry.getObjectClasses().values())
486      {
487        StringBuilder ocLine = new StringBuilder();
488        ocLine.append("# objectClass: ");
489        ocLine.append(s);
490        writeLDIFLine(ocLine, writer, wrapLines, wrapColumn);
491      }
492
493      // Write the set of user attributes.
494      for (AttributeType attrType : entry.getUserAttributes().keySet())
495      {
496        for (Attribute a : entry.getUserAttribute(attrType))
497        {
498          final String attrDesc = a.getAttributeDescription().toString();
499          final StringBuilder attrName = new StringBuilder(2 + attrDesc.length());
500          attrName.append("# ");
501          attrName.append(attrDesc);
502
503          for (ByteString v : a)
504          {
505            writeAttribute(attrName, v, writer, wrapLines, wrapColumn);
506          }
507        }
508      }
509    }
510
511
512    // Make sure there is a blank line after the entry.
513    writer.newLine();
514  }
515
516
517
518  /**
519   * Writes a modify change record with the provided information.  No filtering
520   * will be performed, nor will any export plugins be invoked.
521   *
522   * @param  dn             The DN of the entry being modified.  It must not be
523   *                        <CODE>null</CODE>.
524   * @param  modifications  The set of modifications to include in the change
525   *                        record.  It must not be <CODE>null</CODE>.
526   *
527   * @throws  IOException  If a problem occurs while writing the modify record.
528   */
529  public void writeModifyChangeRecord(DN dn, List<Modification> modifications)
530         throws IOException
531  {
532    ifNull(dn, modifications);
533
534    // If there aren't any modifications, then there's nothing to do.
535    if (modifications.isEmpty())
536    {
537      return;
538    }
539
540
541    // Get the information necessary to write the LDIF.
542    BufferedWriter writer     = exportConfig.getWriter();
543    int            wrapColumn = exportConfig.getWrapColumn();
544    boolean        wrapLines  = wrapColumn > 1;
545
546
547    // Write the DN and changetype.
548    writeDN("dn", dn, writer, wrapLines, wrapColumn);
549
550    StringBuilder changeTypeLine = new StringBuilder("changetype: modify");
551    writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn);
552
553
554    // Iterate through the modifications and write them to the LDIF.
555    Iterator<Modification> iterator = modifications.iterator();
556    while (iterator.hasNext())
557    {
558      Modification m    = iterator.next();
559      Attribute    a    = m.getAttribute();
560
561      String name = a.getAttributeDescription().toString();
562
563      StringBuilder modTypeLine = new StringBuilder();
564      modTypeLine.append(m.getModificationType());
565      modTypeLine.append(": ");
566      modTypeLine.append(name);
567      writeLDIFLine(modTypeLine, writer, wrapLines, wrapColumn);
568
569      for (ByteString v : a)
570      {
571        writeAttribute(name, v, writer, wrapLines, wrapColumn);
572      }
573
574
575      // If this is the last modification, then append blank line.  Otherwise
576      // write a line with just a dash.
577      if (iterator.hasNext())
578      {
579        writer.write("-");
580      }
581      writer.newLine();
582    }
583  }
584
585
586
587  /**
588   * Writes a modify DN change record with the provided information.  No
589   * filtering will be performed, nor will any export plugins be invoked.
590   *
591   * @param  dn            The DN of the entry before the rename.  It must not
592   *                       be <CODE>null</CODE>.
593   * @param  newRDN        The new RDN for the entry.  It must not be
594   *                       <CODE>null</CODE>.
595   * @param  deleteOldRDN  Indicates whether the old RDN value should be removed
596   *                       from the entry.
597   * @param  newSuperior   The new superior DN for the entry, or
598   *                       <CODE>null</CODE> if the entry will stay below the
599   *                       same parent.
600   *
601   * @throws  IOException  If a problem occurs while writing the modify record.
602   */
603  public void writeModifyDNChangeRecord(DN dn, RDN newRDN, boolean deleteOldRDN,
604                                        DN newSuperior)
605         throws IOException
606  {
607    ifNull(dn, newRDN);
608
609
610    // Get the information necessary to write the LDIF.
611    BufferedWriter writer     = exportConfig.getWriter();
612    int            wrapColumn = exportConfig.getWrapColumn();
613    boolean        wrapLines  = wrapColumn > 1;
614
615
616    // Write the current DN.
617    writeDN("dn", dn, writer, wrapLines, wrapColumn);
618
619
620    // Write the changetype.  Some older tools may not support the "moddn"
621    // changetype, so only use it if a newSuperior element has been provided,
622    // but use modrdn elsewhere.
623    String changeType = newSuperior == null ? "changetype: modrdn" : "changetype: moddn";
624    writeLDIFLine(new StringBuilder(changeType), writer, wrapLines, wrapColumn);
625
626
627    // Write the newRDN element.
628    StringBuilder rdnLine = new StringBuilder("newrdn");
629    appendLDIFSeparatorAndValue(rdnLine, ByteString.valueOfUtf8(newRDN.toString()));
630    writeLDIFLine(rdnLine, writer, wrapLines, wrapColumn);
631
632
633    // Write the deleteOldRDN element.
634    StringBuilder deleteOldRDNLine = new StringBuilder();
635    deleteOldRDNLine.append("deleteoldrdn: ");
636    deleteOldRDNLine.append(deleteOldRDN ? "1" : "0");
637    writeLDIFLine(deleteOldRDNLine, writer, wrapLines, wrapColumn);
638
639    if (newSuperior != null)
640    {
641      writeDN("newsuperior", newSuperior, writer, wrapLines, wrapColumn);
642    }
643
644
645    // Make sure there is a blank line after the entry.
646    writer.newLine();
647  }
648
649  private void writeDN(String attrType, DN dn, BufferedWriter writer,
650      boolean wrapLines, int wrapColumn) throws IOException
651  {
652    final StringBuilder newLine = new StringBuilder(attrType);
653    appendLDIFSeparatorAndValue(newLine, ByteString.valueOfUtf8(dn.toString()));
654    writeLDIFLine(newLine, writer, wrapLines, wrapColumn);
655  }
656
657  private void writeAttribute(CharSequence attrName, ByteString v,
658      BufferedWriter writer, boolean wrapLines, int wrapColumn)
659      throws IOException
660  {
661    StringBuilder newLine = new StringBuilder(attrName);
662    appendLDIFSeparatorAndValue(newLine, v);
663    writeLDIFLine(newLine, writer, wrapLines, wrapColumn);
664  }
665
666  /**
667   * Flushes the data written to the output stream or underlying file.
668   *
669   * @throws  IOException  If a problem occurs while flushing the output.
670   */
671  public void flush()
672         throws IOException
673  {
674    writer.flush();
675  }
676
677
678
679  /**
680   * Closes the LDIF writer and the underlying output stream or file.
681   *
682   * @throws  IOException  If a problem occurs while closing the writer.
683   */
684  @Override
685  public void close()
686         throws IOException
687  {
688    writer.flush();
689    writer.close();
690  }
691
692
693
694  /**
695   * Appends an LDIF separator and properly-encoded form of the given
696   * value to the provided buffer.  If the value is safe to include
697   * as-is, then a single colon, a single space, space, and the
698   * provided value will be appended.  Otherwise, two colons, a single
699   * space, and a base64-encoded form of the value will be appended.
700   *
701   * @param  buffer      The buffer to which the information should be
702   *                     appended.  It must not be <CODE>null</CODE>.
703   * @param  valueBytes  The value to append to the buffer.  It must not be
704   *                     <CODE>null</CODE>.
705   */
706  public static void appendLDIFSeparatorAndValue(StringBuilder buffer,
707                                                 ByteSequence valueBytes)
708  {
709    appendLDIFSeparatorAndValue(buffer, valueBytes, false, false);
710  }
711
712  /**
713   * Appends an LDIF separator and properly-encoded form of the given
714   * value to the provided buffer.  If the value is safe to include
715   * as-is, then a single colon, a single space, space, and the
716   * provided value will be appended.  Otherwise, two colons, a single
717   * space, and a base64-encoded form of the value will be appended.
718   * @param  buffer      The buffer to which the information should be
719   *                     appended.  It must not be <CODE>null</CODE>.
720   * @param  valueBytes  The value to append to the buffer.  It must not be
721   *                     <CODE>null</CODE>.
722   * @param isURL        Whether the provided value is an URL value or not.
723   * @param isBase64     Whether the provided value is a base 64 value or not.
724   */
725  public static void appendLDIFSeparatorAndValue(StringBuilder buffer,
726      ByteSequence valueBytes, boolean isURL, boolean isBase64)
727  {
728    ifNull(buffer, valueBytes);
729
730
731    // If the value is empty, then just append a single colon (the URL '<' if
732    // required) and a single space.
733    final boolean valueIsEmpty = valueBytes == null || valueBytes.length() == 0;
734    if (isURL)
735    {
736      buffer.append(":< ");
737      if (!valueIsEmpty)
738      {
739        buffer.append(valueBytes.toString());
740      }
741    }
742    else if (isBase64)
743    {
744      buffer.append(":: ");
745      if (!valueIsEmpty)
746      {
747        buffer.append(valueBytes.toString());
748      }
749    }
750    else if (needsBase64Encoding(valueBytes))
751    {
752      buffer.append(":: ");
753      buffer.append(Base64.encode(valueBytes));
754    }
755    else
756    {
757      buffer.append(": ");
758      if (!valueIsEmpty)
759      {
760        buffer.append(valueBytes.toString());
761      }
762    }
763  }
764
765
766
767  /**
768   * Writes the provided line to LDIF using the provided information.
769   *
770   * @param  line        The line of information to write.  It must not be
771   *                     <CODE>null</CODE>.
772   * @param  writer      The writer to which the data should be written.  It
773   *                     must not be <CODE>null</CODE>.
774   * @param  wrapLines   Indicates whether to wrap long lines.
775   * @param  wrapColumn  The column at which long lines should be wrapped.
776   *
777   * @throws  IOException  If a problem occurs while writing the information.
778   */
779  public static void writeLDIFLine(StringBuilder line, BufferedWriter writer,
780                                   boolean wrapLines, int wrapColumn)
781          throws IOException
782  {
783    ifNull(line, writer);
784
785    int length = line.length();
786    if (wrapLines && length > wrapColumn)
787    {
788      writer.write(line.substring(0, wrapColumn));
789      writer.newLine();
790
791      int pos = wrapColumn;
792      while (pos < length)
793      {
794        int writeLength = Math.min(wrapColumn-1, length-pos);
795        writer.write(' ');
796        writer.write(line.substring(pos, pos+writeLength));
797        writer.newLine();
798
799        pos += wrapColumn-1;
800      }
801    }
802    else
803    {
804      writer.write(line.toString());
805      writer.newLine();
806    }
807  }
808}