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
12 import static java.nio.charset.StandardCharsets.UTF_8;
13
14 import java.io.IOException;
15 import java.io.OutputStream;
16 import java.io.UnsupportedEncodingException;
17 import java.nio.charset.Charset;
18 import java.nio.charset.StandardCharsets;
19 import java.text.MessageFormat;
20 import java.util.Objects;
21
22 import org.eclipse.jgit.annotations.NonNull;
23 import org.eclipse.jgit.annotations.Nullable;
24 import org.eclipse.jgit.internal.JGitText;
25 import org.eclipse.jgit.util.References;
26
27 /**
28 * Common base class for {@link CommitBuilder} and {@link TagBuilder}.
29 *
30 * @since 5.11
31 */
32 public abstract class ObjectBuilder {
33
34 /** Byte representation of "encoding". */
35 private static final byte[] hencoding = Constants.encodeASCII("encoding"); //$NON-NLS-1$
36
37 private PersonIdent author;
38
39 private GpgSignature gpgSignature;
40
41 private String message;
42
43 private Charset encoding = StandardCharsets.UTF_8;
44
45 /**
46 * Retrieves the author of this object.
47 *
48 * @return the author of this object, or {@code null} if not set yet
49 */
50 protected PersonIdent getAuthor() {
51 return author;
52 }
53
54 /**
55 * Sets the author (name, email address, and date) of this object.
56 *
57 * @param newAuthor
58 * the new author, must be non-{@code null}
59 */
60 protected void setAuthor(PersonIdent newAuthor) {
61 author = Objects.requireNonNull(newAuthor);
62 }
63
64 /**
65 * Sets the GPG signature of this object.
66 * <p>
67 * Note, the signature set here will change the payload of the object, i.e.
68 * the output of {@link #build()} will include the signature. Thus, the
69 * typical flow will be:
70 * <ol>
71 * <li>call {@link #build()} without a signature set to obtain payload</li>
72 * <li>create {@link GpgSignature} from payload</li>
73 * <li>set {@link GpgSignature}</li>
74 * </ol>
75 * </p>
76 *
77 * @param gpgSignature
78 * the signature to set or {@code null} to unset
79 * @since 5.3
80 */
81 public void setGpgSignature(@Nullable GpgSignature gpgSignature) {
82 this.gpgSignature = gpgSignature;
83 }
84
85 /**
86 * Retrieves the GPG signature of this object.
87 *
88 * @return the GPG signature of this object, or {@code null} if the object
89 * is not signed
90 * @since 5.3
91 */
92 @Nullable
93 public GpgSignature getGpgSignature() {
94 return gpgSignature;
95 }
96
97 /**
98 * Retrieves the complete message of the object.
99 *
100 * @return the complete message; can be {@code null}.
101 */
102 @Nullable
103 public String getMessage() {
104 return message;
105 }
106
107 /**
108 * Sets the message (commit message, or message of an annotated tag).
109 *
110 * @param message
111 * the message.
112 */
113 public void setMessage(@Nullable String message) {
114 this.message = message;
115 }
116
117 /**
118 * Retrieves the encoding that should be used for the message text.
119 *
120 * @return the encoding that should be used for the message text.
121 */
122 @NonNull
123 public Charset getEncoding() {
124 return encoding;
125 }
126
127 /**
128 * Sets the encoding for the object message.
129 *
130 * @param encoding
131 * the encoding to use.
132 */
133 public void setEncoding(@NonNull Charset encoding) {
134 this.encoding = encoding;
135 }
136
137 /**
138 * Format this builder's state as a git object.
139 *
140 * @return this object in the canonical git format, suitable for storage in
141 * a repository.
142 * @throws java.io.UnsupportedEncodingException
143 * the encoding specified by {@link #getEncoding()} is not
144 * supported by this Java runtime.
145 */
146 @NonNull
147 public abstract byte[] build() throws UnsupportedEncodingException;
148
149 /**
150 * Writes signature to output as per <a href=
151 * "https://github.com/git/git/blob/master/Documentation/technical/signature-format.txt#L66,L89">gpgsig
152 * header</a>.
153 * <p>
154 * CRLF and CR will be sanitized to LF and signature will have a hanging
155 * indent of one space starting with line two. A trailing line break is
156 * <em>not</em> written; the caller is supposed to terminate the GPG
157 * signature header by writing a single newline.
158 * </p>
159 *
160 * @param in
161 * signature string with line breaks
162 * @param out
163 * output stream
164 * @param enforceAscii
165 * whether to throw {@link IllegalArgumentException} if non-ASCII
166 * characters are encountered
167 * @throws IOException
168 * thrown by the output stream
169 * @throws IllegalArgumentException
170 * if the signature string contains non 7-bit ASCII chars and
171 * {@code enforceAscii == true}
172 */
173 static void writeMultiLineHeader(@NonNull String in,
174 @NonNull OutputStream out, boolean enforceAscii)
175 throws IOException, IllegalArgumentException {
176 int length = in.length();
177 for (int i = 0; i < length; ++i) {
178 char ch = in.charAt(i);
179 switch (ch) {
180 case '\r':
181 if (i + 1 < length && in.charAt(i + 1) == '\n') {
182 ++i;
183 }
184 if (i + 1 < length) {
185 out.write('\n');
186 out.write(' ');
187 }
188 break;
189 case '\n':
190 if (i + 1 < length) {
191 out.write('\n');
192 out.write(' ');
193 }
194 break;
195 default:
196 // sanity check
197 if (ch > 127 && enforceAscii)
198 throw new IllegalArgumentException(MessageFormat
199 .format(JGitText.get().notASCIIString, in));
200 out.write(ch);
201 break;
202 }
203 }
204 }
205
206 /**
207 * Writes an "encoding" header.
208 *
209 * @param encoding
210 * to write
211 * @param out
212 * to write to
213 * @throws IOException
214 * if writing fails
215 */
216 static void writeEncoding(@NonNull Charset encoding,
217 @NonNull OutputStream out) throws IOException {
218 if (!References.isSameObject(encoding, UTF_8)) {
219 out.write(hencoding);
220 out.write(' ');
221 out.write(Constants.encodeASCII(encoding.name()));
222 out.write('\n');
223 }
224 }
225 }