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