View Javadoc
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 }