mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
25.33.2012 263d085885df024dca9250cc03c807912b0a7662
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldif/AbstractLDIFWriter.java
@@ -6,17 +6,16 @@
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opendj3/legal-notices/CDDLv1_0.txt
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opendj3/legal-notices/CDDLv1_0.txt.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
@@ -28,8 +27,6 @@
package org.forgerock.opendj.ldif;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
@@ -45,503 +42,388 @@
import com.forgerock.opendj.util.Base64;
import com.forgerock.opendj.util.Validator;
/**
 * Common LDIF writer functionality.
 */
abstract class AbstractLDIFWriter extends AbstractLDIFStream
{
  /**
   * LDIF writer implementation interface.
   */
  interface LDIFWriterImpl
  {
abstract class AbstractLDIFWriter extends AbstractLDIFStream {
    /**
     * Closes any resources associated with this LDIF writer implementation.
     *
     * @throws IOException
     *           If an error occurs while closing.
     * LDIF writer implementation interface.
     */
    void close() throws IOException;
    interface LDIFWriterImpl {
        /**
         * Closes any resources associated with this LDIF writer implementation.
         *
         * @throws IOException
         *             If an error occurs while closing.
         */
        void close() throws IOException;
        /**
         * Flushes this LDIF writer implementation so that any buffered data is
         * written immediately to underlying stream, flushing the stream if it
         * is also {@code Flushable}.
         * <p>
         * If the intended destination of this stream is an abstraction provided
         * by the underlying operating system, for example a file, then flushing
         * the stream guarantees only that bytes previously written to the
         * stream are passed to the operating system for writing; it does not
         * guarantee that they are actually written to a physical device such as
         * a disk drive.
         *
         * @throws IOException
         *             If an error occurs while flushing.
         */
        void flush() throws IOException;
        /**
         * Prints the provided {@code CharSequence}. Implementations must not
         * add a new-line character sequence.
         *
         * @param s
         *            The {@code CharSequence} to be printed.
         * @throws IOException
         *             If an error occurs while printing {@code s}.
         */
        void print(CharSequence s) throws IOException;
        /**
         * Prints a new-line character sequence.
         *
         * @throws IOException
         *             If an error occurs while printing the new-line character
         *             sequence.
         */
        void println() throws IOException;
    }
    /**
     * Flushes this LDIF writer implementation so that any buffered data is
     * written immediately to underlying stream, flushing the stream if it is
     * also {@code Flushable}.
     * <p>
     * If the intended destination of this stream is an abstraction provided by
     * the underlying operating system, for example a file, then flushing the
     * stream guarantees only that bytes previously written to the stream are
     * passed to the operating system for writing; it does not guarantee that
     * they are actually written to a physical device such as a disk drive.
     *
     * @throws IOException
     *           If an error occurs while flushing.
     * LDIF string list writer implementation.
     */
    void flush() throws IOException;
    private static final class LDIFWriterListImpl implements LDIFWriterImpl {
        private final StringBuilder builder = new StringBuilder();
        private final List<String> ldifLines;
        /**
         * Creates a new LDIF list writer.
         *
         * @param ldifLines
         *            The string list.
         */
        LDIFWriterListImpl(final List<String> ldifLines) {
            this.ldifLines = ldifLines;
        }
        /**
         * {@inheritDoc}
         */
        public void close() throws IOException {
            // Nothing to do.
        }
        /**
         * {@inheritDoc}
         */
        public void flush() throws IOException {
            // Nothing to do.
        }
        /**
         * {@inheritDoc}
         */
        public void print(final CharSequence s) throws IOException {
            builder.append(s);
        }
        /**
         * {@inheritDoc}
         */
        public void println() throws IOException {
            ldifLines.add(builder.toString());
            builder.setLength(0);
        }
    }
    /**
     * Prints the provided {@code CharSequence}. Implementations must not add a
     * new-line character sequence.
     *
     * @param s
     *          The {@code CharSequence} to be printed.
     * @throws IOException
     *           If an error occurs while printing {@code s}.
     * LDIF output stream writer implementation.
     */
    void print(CharSequence s) throws IOException;
    private static final class LDIFWriterOutputStreamImpl implements LDIFWriterImpl {
        private final BufferedWriter writer;
        /**
         * Creates a new LDIF output stream writer.
         *
         * @param out
         *            The output stream.
         */
        LDIFWriterOutputStreamImpl(final OutputStream out) {
            this.writer = new BufferedWriter(new OutputStreamWriter(out));
        }
        /**
         * {@inheritDoc}
         */
        public void close() throws IOException {
            writer.close();
        }
        /**
         * {@inheritDoc}
         */
        public void flush() throws IOException {
            writer.flush();
        }
        /**
         * {@inheritDoc}
         */
        public void print(final CharSequence s) throws IOException {
            writer.append(s);
        }
        /**
         * {@inheritDoc}
         */
        public void println() throws IOException {
            writer.newLine();
        }
    }
    // Regular expression used for splitting comments on line-breaks.
    private static final Pattern SPLIT_NEWLINE = Pattern.compile("\\r?\\n");
    boolean addUserFriendlyComments = false;
    final LDIFWriterImpl impl;
    int wrapColumn = 0;
    private final StringBuilder builder = new StringBuilder(80);
    Schema schema = Schema.getDefaultSchema();
    /**
     * Prints a new-line character sequence.
     *
     * @throws IOException
     *           If an error occurs while printing the new-line character
     *           sequence.
     */
    void println() throws IOException;
  }
  /**
   * LDIF string list writer implementation.
   */
  private static final class LDIFWriterListImpl implements LDIFWriterImpl
  {
    private final StringBuilder builder = new StringBuilder();
    private final List<String> ldifLines;
    /**
     * Creates a new LDIF list writer.
     * Creates a new LDIF entry writer which will append lines of LDIF to the
     * provided list.
     *
     * @param ldifLines
     *          The string list.
     *            The list to which lines of LDIF should be appended.
     */
    LDIFWriterListImpl(final List<String> ldifLines)
    {
      this.ldifLines = ldifLines;
    public AbstractLDIFWriter(final List<String> ldifLines) {
        Validator.ensureNotNull(ldifLines);
        this.impl = new LDIFWriterListImpl(ldifLines);
    }
    /**
     * {@inheritDoc}
     */
    public void close() throws IOException
    {
      // Nothing to do.
    }
    /**
     * {@inheritDoc}
     */
    public void flush() throws IOException
    {
      // Nothing to do.
    }
    /**
     * {@inheritDoc}
     */
    public void print(final CharSequence s) throws IOException
    {
      builder.append(s);
    }
    /**
     * {@inheritDoc}
     */
    public void println() throws IOException
    {
      ldifLines.add(builder.toString());
      builder.setLength(0);
    }
  }
  /**
   * LDIF output stream writer implementation.
   */
  private static final class LDIFWriterOutputStreamImpl implements
      LDIFWriterImpl
  {
    private final BufferedWriter writer;
    /**
     * Creates a new LDIF output stream writer.
     * Creates a new LDIF entry writer whose destination is the provided output
     * stream.
     *
     * @param out
     *          The output stream.
     *            The output stream to use.
     */
    LDIFWriterOutputStreamImpl(final OutputStream out)
    {
      this.writer = new BufferedWriter(new OutputStreamWriter(out));
    public AbstractLDIFWriter(final OutputStream out) {
        Validator.ensureNotNull(out);
        this.impl = new LDIFWriterOutputStreamImpl(out);
    }
    /**
     * {@inheritDoc}
     */
    public void close() throws IOException
    {
      writer.close();
    final void close0() throws IOException {
        flush0();
        impl.close();
    }
    /**
     * {@inheritDoc}
     */
    public void flush() throws IOException
    {
      writer.flush();
    final void flush0() throws IOException {
        impl.flush();
    }
    final void writeComment0(final CharSequence comment) throws IOException {
        Validator.ensureNotNull(comment);
        // First, break up the comment into multiple lines to preserve the
        // original spacing that it contained.
        final String[] lines = SPLIT_NEWLINE.split(comment);
    /**
     * {@inheritDoc}
     */
    public void print(final CharSequence s) throws IOException
    {
      writer.append(s);
    }
        // Now iterate through the lines and write them out, prefixing and
        // wrapping them as necessary.
        for (final String line : lines) {
            if (!shouldWrap()) {
                impl.print("# ");
                impl.print(line);
                impl.println();
            } else {
                final int breakColumn = wrapColumn - 2;
                if (line.length() <= breakColumn) {
                    impl.print("# ");
                    impl.print(line);
                    impl.println();
                } else {
                    int startPos = 0;
                outerLoop:
                    while (startPos < line.length()) {
                        if (startPos + breakColumn >= line.length()) {
                            impl.print("# ");
                            impl.print(line.substring(startPos));
                            impl.println();
                            startPos = line.length();
                        } else {
                            final int endPos = startPos + breakColumn;
                            int i = endPos - 1;
                            while (i > startPos) {
                                if (line.charAt(i) == ' ') {
                                    impl.print("# ");
                                    impl.print(line.substring(startPos, i));
                                    impl.println();
    /**
     * {@inheritDoc}
     */
    public void println() throws IOException
    {
      writer.newLine();
    }
  }
                                    startPos = i + 1;
                                    continue outerLoop;
                                }
                                i--;
                            }
                            // If we've gotten here, then there are no spaces on
                            // the
                            // entire line. If that happens, then we'll have to
                            // break
                            // in the middle of a word.
                            impl.print("# ");
                            impl.print(line.substring(startPos, endPos));
                            impl.println();
  // Regular expression used for splitting comments on line-breaks.
  private static final Pattern SPLIT_NEWLINE = Pattern.compile("\\r?\\n");
  boolean addUserFriendlyComments = false;
  final LDIFWriterImpl impl;
  int wrapColumn = 0;
  private final StringBuilder builder = new StringBuilder(80);
  Schema schema = Schema.getDefaultSchema();
  /**
   * Creates a new LDIF entry writer which will append lines of LDIF to the
   * provided list.
   *
   * @param ldifLines
   *          The list to which lines of LDIF should be appended.
   */
  public AbstractLDIFWriter(final List<String> ldifLines)
  {
    Validator.ensureNotNull(ldifLines);
    this.impl = new LDIFWriterListImpl(ldifLines);
  }
  /**
   * Creates a new LDIF entry writer whose destination is the provided output
   * stream.
   *
   * @param out
   *          The output stream to use.
   */
  public AbstractLDIFWriter(final OutputStream out)
  {
    Validator.ensureNotNull(out);
    this.impl = new LDIFWriterOutputStreamImpl(out);
  }
  final void close0() throws IOException
  {
    flush0();
    impl.close();
  }
  final void flush0() throws IOException
  {
    impl.flush();
  }
  final void writeComment0(final CharSequence comment) throws IOException
  {
    Validator.ensureNotNull(comment);
    // First, break up the comment into multiple lines to preserve the
    // original spacing that it contained.
    final String[] lines = SPLIT_NEWLINE.split(comment);
    // Now iterate through the lines and write them out, prefixing and
    // wrapping them as necessary.
    for (final String line : lines)
    {
      if (!shouldWrap())
      {
        impl.print("# ");
        impl.print(line);
        impl.println();
      }
      else
      {
        final int breakColumn = wrapColumn - 2;
        if (line.length() <= breakColumn)
        {
          impl.print("# ");
          impl.print(line);
          impl.println();
        }
        else
        {
          int startPos = 0;
          outerLoop: while (startPos < line.length())
          {
            if (startPos + breakColumn >= line.length())
            {
              impl.print("# ");
              impl.print(line.substring(startPos));
              impl.println();
              startPos = line.length();
            }
            else
            {
              final int endPos = startPos + breakColumn;
              int i = endPos - 1;
              while (i > startPos)
              {
                if (line.charAt(i) == ' ')
                {
                  impl.print("# ");
                  impl.print(line.substring(startPos, i));
                  impl.println();
                  startPos = i + 1;
                  continue outerLoop;
                            startPos = endPos;
                        }
                    }
                }
                i--;
              }
              // If we've gotten here, then there are no spaces on the
              // entire line. If that happens, then we'll have to break
              // in the middle of a word.
              impl.print("# ");
              impl.print(line.substring(startPos, endPos));
              impl.println();
              startPos = endPos;
            }
          }
        }
      }
    }
  }
  final void writeControls(final List<Control> controls) throws IOException
  {
    for (final Control control : controls)
    {
      final StringBuilder key = new StringBuilder("control: ");
      key.append(control.getOID());
      key.append(control.isCritical() ? " true" : " false");
      if (control.hasValue())
      {
        writeKeyAndValue(key, control.getValue());
      }
      else
      {
        writeLine(key);
      }
    }
  }
  final void writeKeyAndValue(final CharSequence key, final ByteSequence value)
      throws IOException
  {
    builder.setLength(0);
    // If the value is empty, then just append a single colon and a
    // single space.
    if (value.length() == 0)
    {
      builder.append(key);
      builder.append(": ");
    }
    else if (needsBase64Encoding(value))
    {
      if (addUserFriendlyComments)
      {
        // TODO: Only display comments for valid UTF-8 values, not
        // binary values.
      }
      builder.setLength(0);
      builder.append(key);
      builder.append(":: ");
      builder.append(Base64.encode(value));
    }
    else
    {
      builder.append(key);
      builder.append(": ");
      builder.append(value.toString());
    }
    writeLine(builder);
  }
    final void writeControls(final List<Control> controls) throws IOException {
        for (final Control control : controls) {
            final StringBuilder key = new StringBuilder("control: ");
            key.append(control.getOID());
            key.append(control.isCritical() ? " true" : " false");
  final void writeKeyAndValue(final CharSequence key, final CharSequence value)
      throws IOException
  {
    // FIXME: We should optimize this at some point.
    writeKeyAndValue(key, ByteString.valueOf(value.toString()));
  }
  final void writeLine(final CharSequence line) throws IOException
  {
    final int length = line.length();
    if (shouldWrap() && length > wrapColumn)
    {
      impl.print(line.subSequence(0, wrapColumn));
      impl.println();
      int pos = wrapColumn;
      while (pos < length)
      {
        final int writeLength = Math.min(wrapColumn - 1, length - pos);
        impl.print(" ");
        impl.print(line.subSequence(pos, pos + writeLength));
        impl.println();
        pos += wrapColumn - 1;
      }
    }
    else
    {
      impl.print(line);
      impl.println();
    }
  }
  private boolean needsBase64Encoding(final ByteSequence bytes)
  {
    final int length = bytes.length();
    if (length == 0)
    {
      return false;
            if (control.hasValue()) {
                writeKeyAndValue(key, control.getValue());
            } else {
                writeLine(key);
            }
        }
    }
    // If the value starts with a space, colon, or less than, then it
    // needs to be base64 encoded.
    switch (bytes.byteAt(0))
    {
    case 0x20: // Space
    case 0x3A: // Colon
    case 0x3C: // Less-than
      return true;
    final void writeKeyAndValue(final CharSequence key, final ByteSequence value)
            throws IOException {
        builder.setLength(0);
        // If the value is empty, then just append a single colon and a
        // single space.
        if (value.length() == 0) {
            builder.append(key);
            builder.append(": ");
        } else if (needsBase64Encoding(value)) {
            if (addUserFriendlyComments) {
                // TODO: Only display comments for valid UTF-8 values, not
                // binary values.
            }
            builder.setLength(0);
            builder.append(key);
            builder.append(":: ");
            builder.append(Base64.encode(value));
        } else {
            builder.append(key);
            builder.append(": ");
            builder.append(value.toString());
        }
        writeLine(builder);
    }
    // If the value ends with a space, then it needs to be
    // base64 encoded.
    if (length > 1 && bytes.byteAt(length - 1) == 0x20)
    {
      return true;
    final void writeKeyAndValue(final CharSequence key, final CharSequence value)
            throws IOException {
        // FIXME: We should optimize this at some point.
        writeKeyAndValue(key, ByteString.valueOf(value.toString()));
    }
    // If the value contains a null, newline, or return character, then
    // it needs to be base64 encoded.
    byte b;
    for (int i = 0; i < bytes.length(); i++)
    {
      b = bytes.byteAt(i);
      if (b > 127 || b < 0)
      {
        return true;
      }
      switch (b)
      {
      case 0x00: // Null
      case 0x0A: // New line
      case 0x0D: // Carriage return
        return true;
      }
    final void writeLine(final CharSequence line) throws IOException {
        final int length = line.length();
        if (shouldWrap() && length > wrapColumn) {
            impl.print(line.subSequence(0, wrapColumn));
            impl.println();
            int pos = wrapColumn;
            while (pos < length) {
                final int writeLength = Math.min(wrapColumn - 1, length - pos);
                impl.print(" ");
                impl.print(line.subSequence(pos, pos + writeLength));
                impl.println();
                pos += wrapColumn - 1;
            }
        } else {
            impl.print(line);
            impl.println();
        }
    }
    // If we've made it here, then there's no reason to base64 encode.
    return false;
  }
    private boolean needsBase64Encoding(final ByteSequence bytes) {
        final int length = bytes.length();
        if (length == 0) {
            return false;
        }
        // If the value starts with a space, colon, or less than, then it
        // needs to be base64 encoded.
        switch (bytes.byteAt(0)) {
        case 0x20: // Space
        case 0x3A: // Colon
        case 0x3C: // Less-than
            return true;
        }
        // If the value ends with a space, then it needs to be
        // base64 encoded.
        if (length > 1 && bytes.byteAt(length - 1) == 0x20) {
            return true;
        }
  private boolean shouldWrap()
  {
    return wrapColumn > 1;
  }
        // If the value contains a null, newline, or return character, then
        // it needs to be base64 encoded.
        byte b;
        for (int i = 0; i < bytes.length(); i++) {
            b = bytes.byteAt(i);
            if (b > 127 || b < 0) {
                return true;
            }
            switch (b) {
            case 0x00: // Null
            case 0x0A: // New line
            case 0x0D: // Carriage return
                return true;
            }
        }
        // If we've made it here, then there's no reason to base64 encode.
        return false;
    }
  @SuppressWarnings("unused")
  private void writeKeyAndURL(final CharSequence key, final CharSequence url)
      throws IOException
  {
    builder.setLength(0);
    private boolean shouldWrap() {
        return wrapColumn > 1;
    }
    builder.append(key);
    builder.append(":: ");
    builder.append(url);
    @SuppressWarnings("unused")
    private void writeKeyAndURL(final CharSequence key, final CharSequence url) throws IOException {
        builder.setLength(0);
    writeLine(builder);
  }
        builder.append(key);
        builder.append(":: ");
        builder.append(url);
        writeLine(builder);
    }
}