ObjectBuilder.java

  1. /*
  2.  * Copyright (C) 2020, Thomas Wolf <thomas.wolf@paranor.ch> and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */
  10. package org.eclipse.jgit.lib;

  11. import static java.nio.charset.StandardCharsets.UTF_8;

  12. import java.io.IOException;
  13. import java.io.OutputStream;
  14. import java.io.UnsupportedEncodingException;
  15. import java.nio.charset.Charset;
  16. import java.nio.charset.StandardCharsets;
  17. import java.text.MessageFormat;
  18. import java.util.Objects;

  19. import org.eclipse.jgit.annotations.NonNull;
  20. import org.eclipse.jgit.annotations.Nullable;
  21. import org.eclipse.jgit.internal.JGitText;
  22. import org.eclipse.jgit.util.References;

  23. /**
  24.  * Common base class for {@link CommitBuilder} and {@link TagBuilder}.
  25.  *
  26.  * @since 5.11
  27.  */
  28. public abstract class ObjectBuilder {

  29.     /** Byte representation of "encoding". */
  30.     private static final byte[] hencoding = Constants.encodeASCII("encoding"); //$NON-NLS-1$

  31.     private PersonIdent author;

  32.     private GpgSignature gpgSignature;

  33.     private String message;

  34.     private Charset encoding = StandardCharsets.UTF_8;

  35.     /**
  36.      * Retrieves the author of this object.
  37.      *
  38.      * @return the author of this object, or {@code null} if not set yet
  39.      */
  40.     protected PersonIdent getAuthor() {
  41.         return author;
  42.     }

  43.     /**
  44.      * Sets the author (name, email address, and date) of this object.
  45.      *
  46.      * @param newAuthor
  47.      *            the new author, must be non-{@code null}
  48.      */
  49.     protected void setAuthor(PersonIdent newAuthor) {
  50.         author = Objects.requireNonNull(newAuthor);
  51.     }

  52.     /**
  53.      * Sets the GPG signature of this object.
  54.      * <p>
  55.      * Note, the signature set here will change the payload of the object, i.e.
  56.      * the output of {@link #build()} will include the signature. Thus, the
  57.      * typical flow will be:
  58.      * <ol>
  59.      * <li>call {@link #build()} without a signature set to obtain payload</li>
  60.      * <li>create {@link GpgSignature} from payload</li>
  61.      * <li>set {@link GpgSignature}</li>
  62.      * </ol>
  63.      * </p>
  64.      *
  65.      * @param gpgSignature
  66.      *            the signature to set or {@code null} to unset
  67.      * @since 5.3
  68.      */
  69.     public void setGpgSignature(@Nullable GpgSignature gpgSignature) {
  70.         this.gpgSignature = gpgSignature;
  71.     }

  72.     /**
  73.      * Retrieves the GPG signature of this object.
  74.      *
  75.      * @return the GPG signature of this object, or {@code null} if the object
  76.      *         is not signed
  77.      * @since 5.3
  78.      */
  79.     @Nullable
  80.     public GpgSignature getGpgSignature() {
  81.         return gpgSignature;
  82.     }

  83.     /**
  84.      * Retrieves the complete message of the object.
  85.      *
  86.      * @return the complete message; can be {@code null}.
  87.      */
  88.     @Nullable
  89.     public String getMessage() {
  90.         return message;
  91.     }

  92.     /**
  93.      * Sets the message (commit message, or message of an annotated tag).
  94.      *
  95.      * @param message
  96.      *            the message.
  97.      */
  98.     public void setMessage(@Nullable String message) {
  99.         this.message = message;
  100.     }

  101.     /**
  102.      * Retrieves the encoding that should be used for the message text.
  103.      *
  104.      * @return the encoding that should be used for the message text.
  105.      */
  106.     @NonNull
  107.     public Charset getEncoding() {
  108.         return encoding;
  109.     }

  110.     /**
  111.      * Sets the encoding for the object message.
  112.      *
  113.      * @param encoding
  114.      *            the encoding to use.
  115.      */
  116.     public void setEncoding(@NonNull Charset encoding) {
  117.         this.encoding = encoding;
  118.     }

  119.     /**
  120.      * Format this builder's state as a git object.
  121.      *
  122.      * @return this object in the canonical git format, suitable for storage in
  123.      *         a repository.
  124.      * @throws java.io.UnsupportedEncodingException
  125.      *             the encoding specified by {@link #getEncoding()} is not
  126.      *             supported by this Java runtime.
  127.      */
  128.     @NonNull
  129.     public abstract byte[] build() throws UnsupportedEncodingException;

  130.     /**
  131.      * Writes signature to output as per <a href=
  132.      * "https://github.com/git/git/blob/master/Documentation/technical/signature-format.txt#L66,L89">gpgsig
  133.      * header</a>.
  134.      * <p>
  135.      * CRLF and CR will be sanitized to LF and signature will have a hanging
  136.      * indent of one space starting with line two. A trailing line break is
  137.      * <em>not</em> written; the caller is supposed to terminate the GPG
  138.      * signature header by writing a single newline.
  139.      * </p>
  140.      *
  141.      * @param in
  142.      *            signature string with line breaks
  143.      * @param out
  144.      *            output stream
  145.      * @param enforceAscii
  146.      *            whether to throw {@link IllegalArgumentException} if non-ASCII
  147.      *            characters are encountered
  148.      * @throws IOException
  149.      *             thrown by the output stream
  150.      * @throws IllegalArgumentException
  151.      *             if the signature string contains non 7-bit ASCII chars and
  152.      *             {@code enforceAscii == true}
  153.      */
  154.     static void writeMultiLineHeader(@NonNull String in,
  155.             @NonNull OutputStream out, boolean enforceAscii)
  156.             throws IOException, IllegalArgumentException {
  157.         int length = in.length();
  158.         for (int i = 0; i < length; ++i) {
  159.             char ch = in.charAt(i);
  160.             switch (ch) {
  161.             case '\r':
  162.                 if (i + 1 < length && in.charAt(i + 1) == '\n') {
  163.                     ++i;
  164.                 }
  165.                 if (i + 1 < length) {
  166.                     out.write('\n');
  167.                     out.write(' ');
  168.                 }
  169.                 break;
  170.             case '\n':
  171.                 if (i + 1 < length) {
  172.                     out.write('\n');
  173.                     out.write(' ');
  174.                 }
  175.                 break;
  176.             default:
  177.                 // sanity check
  178.                 if (ch > 127 && enforceAscii)
  179.                     throw new IllegalArgumentException(MessageFormat
  180.                             .format(JGitText.get().notASCIIString, in));
  181.                 out.write(ch);
  182.                 break;
  183.             }
  184.         }
  185.     }

  186.     /**
  187.      * Writes an "encoding" header.
  188.      *
  189.      * @param encoding
  190.      *            to write
  191.      * @param out
  192.      *            to write to
  193.      * @throws IOException
  194.      *             if writing fails
  195.      */
  196.     static void writeEncoding(@NonNull Charset encoding,
  197.             @NonNull OutputStream out) throws IOException {
  198.         if (!References.isSameObject(encoding, UTF_8)) {
  199.             out.write(hencoding);
  200.             out.write(' ');
  201.             out.write(Constants.encodeASCII(encoding.name()));
  202.             out.write('\n');
  203.         }
  204.     }
  205. }